diff options
Diffstat (limited to 'usr/src/lib/libpkg/common')
47 files changed, 25614 insertions, 0 deletions
diff --git a/usr/src/lib/libpkg/common/canonize.c b/usr/src/lib/libpkg/common/canonize.c new file mode 100644 index 0000000000..ff34c40507 --- /dev/null +++ b/usr/src/lib/libpkg/common/canonize.c @@ -0,0 +1,97 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#include <string.h> + +#define isdot(x) ((x[0] == '.') && (!x[1] || (x[1] == '/'))) +#define isdotdot(x) ((x[0] == '.') && (x[1] == '.') && \ + (!x[2] || (x[2] == '/'))) + +void +canonize(char *file) +{ + char *pt, *last; + int level; + + /* remove references such as "./" and "../" and "//" */ + for (pt = file; *pt; /* void */) { + if (isdot(pt)) + (void) strcpy(pt, pt[1] ? pt+2 : pt+1); + else if (isdotdot(pt)) { + level = 0; + last = pt; + do { + level++; + last += 2; + if (*last) + last++; + } while (isdotdot(last)); + --pt; /* point to previous '/' */ + while (level--) { + if (pt <= file) + return; + while ((*--pt != '/') && (pt > file)) + ; + } + if (*pt == '/') + pt++; + (void) strcpy(pt, last); + } else { + while (*pt && (*pt != '/')) + pt++; + if (*pt == '/') { + while (pt[1] == '/') + (void) strcpy(pt, pt+1); + pt++; + } + } + } + if ((--pt > file) && (*pt == '/')) + *pt = '\0'; +} + +void +canonize_slashes(char *file) +{ + char *pt; + + /* remove references such as "//" */ + for (pt = file; *pt; /* void */) { + while (*pt && (*pt != '/')) + pt++; + if (*pt == '/') { + while (pt[1] == '/') + (void) strcpy(pt, pt+1); + pt++; + } + } + if ((--pt > file) && (*pt == '/')) + *pt = '\0'; +} diff --git a/usr/src/lib/libpkg/common/cfext.h b/usr/src/lib/libpkg/common/cfext.h new file mode 100644 index 0000000000..14ec1fce2a --- /dev/null +++ b/usr/src/lib/libpkg/common/cfext.h @@ -0,0 +1,83 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _CFEXT_H +#define _CFEXT_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pkgstrct.h> + +struct mergstat { + unsigned setuid:1; /* pkgmap entry has setuid */ + unsigned setgid:1; /* ... and/or setgid bit set */ + unsigned contchg:1; /* contents of the files different */ + unsigned attrchg:1; /* attributes are different */ + unsigned shared:1; /* > 1 pkg associated with this */ + unsigned osetuid:1; /* installed set[ug]id process ... */ + unsigned osetgid:1; /* ... being overwritten by pkg. */ + unsigned rogue:1; /* conflicting file not owned by a package */ + unsigned dir2nondir:1; /* was a directory & now a non-directory */ + unsigned replace:1; /* merge makes no sense for this object pair */ + unsigned denied:1; /* for some reason this was not allowed in */ + unsigned preloaded:1; /* already checked in a prior pkg op */ + unsigned processed:1; /* already installed or removed */ + unsigned parentsyml2dir:1; + /* parent directory changed from symlink to a directory */ +}; + +/* + * This is information required by pkgadd for fast operation. A + * cfextra struct is tagged to each cfent structure requiring + * processing. This is how we avoid some unneeded repetition. The + * entries incorporating the word 'local' refer to the path that + * gets us to the delivered package file. In other words, to install + * a file we usually copy from 'local' to 'path' below. In the case + * of a link, where no actual copying takes place, local is the source + * of the link. Note that environment variables are not evaluated in + * the locals unless they are links since the literal path is how + * pkgadd finds the entry under the reloc directory. + */ +struct cfextra { + struct cfent cf_ent; /* basic contents file entry */ + struct mergstat mstat; /* merge status for installs */ + short fsys_value; /* fstab[] entry index */ + short fsys_base; /* actual base filesystem in fs_tab[] */ + char *client_path; /* the client-relative path */ + char *server_path; /* the server-relative path */ + char *map_path; /* as read from the pkgmap */ + char *client_local; /* client_relative local */ + char *server_local; /* server relative local */ +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _CFEXT_H */ diff --git a/usr/src/lib/libpkg/common/ckparam.c b/usr/src/lib/libpkg/common/ckparam.c new file mode 100644 index 0000000000..68284e6012 --- /dev/null +++ b/usr/src/lib/libpkg/common/ckparam.c @@ -0,0 +1,196 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" + +#define MAXLEN 256 +#define TOKLEN 16 + +static int proc_name(char *param, char *value); +static int proc_arch(char *param, char *value); +static int proc_version(char *param, char *value); +static int proc_category(char *param, char *value); +static int bad_first_char(char *param, char *value); +static int not_alnum(char *param, char *pt); +static int not_ascii(char *param, char *pt); +static int too_long(char *param, char *pt, int len); +static int isnull(char *param, char *pt); + +int +ckparam(char *param, char *val) +{ + char *value = strdup(val); + int ret_val = 0; /* return value */ + + if (strcmp(param, "NAME") == 0) + ret_val = proc_name(param, value); + + else if (strcmp(param, "ARCH") == 0) + ret_val = proc_arch(param, value); + + else if (strcmp(param, "VERSION") == 0) + ret_val = proc_version(param, value); + + else if (strcmp(param, "CATEGORY") == 0) + ret_val = proc_category(param, value); + + /* param does not match existing parameters */ + free(value); + return (ret_val); +} + +static int +proc_name(char *param, char *value) +{ + int ret_val; + + if (!(ret_val = isnull(param, value))) { + ret_val += too_long(param, value, MAXLEN); + ret_val += not_ascii(param, value); + } + + return (ret_val); +} + +static int +proc_arch(char *param, char *value) +{ + int ret_val; + char *token; + + if (!(ret_val = isnull(param, value))) { + token = strtok(value, ", "); + + while (token) { + ret_val += too_long(param, token, TOKLEN); + ret_val += not_ascii(param, token); + token = strtok(NULL, ", "); + } + } + + return (ret_val); +} + +static int +proc_version(char *param, char *value) +{ + int ret_val; + + if (!(ret_val = isnull(param, value))) { + ret_val += bad_first_char(param, value); + ret_val += too_long(param, value, MAXLEN); + ret_val += not_ascii(param, value); + } + + return (ret_val); +} + +static int +proc_category(char *param, char *value) +{ + int ret_val; + char *token; + + if (!(ret_val = isnull(param, value))) { + token = strtok(value, ", "); + + while (token) { + ret_val += too_long(param, token, TOKLEN); + ret_val += not_alnum(param, token); + token = strtok(NULL, ", "); + } + } + + return (ret_val); +} + +static int +bad_first_char(char *param, char *value) +{ + if (*value == '(') { + progerr(pkg_gt(ERR_CHAR), param); + return (1); + } + + return (0); +} + +static int +isnull(char *param, char *pt) +{ + if (!pt || *pt == '\0') { + progerr(pkg_gt(ERR_UNDEF), param); + return (1); + } + return (0); +} + +static int +too_long(char *param, char *pt, int len) +{ + if (strlen(pt) > (size_t)len) { + progerr(pkg_gt(ERR_LEN), pt); + return (1); + } + return (0); +} + +static int +not_ascii(char *param, char *pt) +{ + while (*pt) { + if (!(isascii(*pt))) { + progerr(pkg_gt(ERR_ASCII), param); + return (1); + } + pt++; + } + return (0); +} + +static int +not_alnum(char *param, char *pt) +{ + while (*pt) { + if (!(isalnum(*pt))) { + progerr(pkg_gt(ERR_ALNUM), param); + return (1); + } + pt++; + } + + return (0); +} diff --git a/usr/src/lib/libpkg/common/ckvolseq.c b/usr/src/lib/libpkg/common/ckvolseq.c new file mode 100644 index 0000000000..13015ea898 --- /dev/null +++ b/usr/src/lib/libpkg/common/ckvolseq.c @@ -0,0 +1,109 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include "pkgstrct.h" +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" + +#define PKGMAP "pkgmap" +#define PKGINFO "pkginfo" + +int +ckvolseq(char *dir, int part, int nparts) +{ + static struct cinfo cinfo; + char ftype, path[PATH_MAX]; + + if (part > 0) { + ftype = 'f'; + if (part == 1) { + /* + * save stats about content information of pkginfo + * file in order to verify multi-volume packages + */ + cinfo.cksum = cinfo.size = cinfo.modtime = (-1L); + (void) snprintf(path, sizeof (path), "%s/pkginfo", dir); + if (cverify(0, &ftype, path, &cinfo, 1)) { + logerr(pkg_gt(ERR_BADPKGINFO), path); + logerr(getErrbufAddr()); + return (1); + } + (void) snprintf(path, sizeof (path), "%s/pkgmap", dir); + if (access(path, 0)) { + logerr(pkg_gt(ERR_NOPKGMAP), path); + return (2); + } + } else { + /* temp fix due to summit problem */ + cinfo.modtime = (-1); + + /* pkginfo file doesn't match first floppy */ + (void) snprintf(path, sizeof (path), "%s/pkginfo", dir); + if (cverify(0, &ftype, path, &cinfo, 1)) { + logerr(pkg_gt(MSG_CORRUPT)); + logerr(getErrbufAddr()); + return (1); + } + } + } else + part = (-part); + + /* + * each volume in a multi-volume package must + * contain either the root.n or reloc.n directories + */ + if (nparts != 1) { + /* look for multi-volume specification */ + (void) snprintf(path, sizeof (path), "%s/root.%d", dir, part); + if (access(path, 0) == 0) + return (0); + (void) snprintf(path, sizeof (path), "%s/reloc.%d", dir, part); + if (access(path, 0) == 0) + return (0); + if (part == 1) { + (void) snprintf(path, sizeof (path), "%s/install", + dir, part); + if (access(path, 0) == 0) + return (0); + } + if (nparts) { + logerr(pkg_gt(MSG_SEQ)); + return (2); + } + } + return (0); +} diff --git a/usr/src/lib/libpkg/common/cvtpath.c b/usr/src/lib/libpkg/common/cvtpath.c new file mode 100644 index 0000000000..1912e24523 --- /dev/null +++ b/usr/src/lib/libpkg/common/cvtpath.c @@ -0,0 +1,58 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <string.h> + +extern char *root, *basedir; /* WHERE? */ + +void +cvtpath(char *path, char *copy) +{ + *copy++ = '/'; + if (root || (basedir && (*path != '/'))) { + if (root && ((basedir == NULL) || (path[0] == '/') || + (basedir[0] != '/'))) { + /* look in root */ + (void) strcpy(copy, root + (*root == '/' ? 1 : 0)); + copy += strlen(copy); + if (copy[-1] != '/') + *copy++ = '/'; + } + if (basedir && (*path != '/')) { + (void) strcpy(copy, + basedir + (*basedir == '/' ? 1 : 0)); + copy += strlen(copy); + if (copy[-1] != '/') + *copy++ = '/'; + } + } + (void) strcpy(copy, path + (*path == '/' ? 1 : 0)); +} diff --git a/usr/src/lib/libpkg/common/dbtables.h b/usr/src/lib/libpkg/common/dbtables.h new file mode 100644 index 0000000000..67b3b0fd20 --- /dev/null +++ b/usr/src/lib/libpkg/common/dbtables.h @@ -0,0 +1,210 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _DBTABLES_H +#define _DBTABLES_H + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TBL_HDR "create table" +#define TBL_TRL "on conflict replace" + +/* + * The patch contents table. This table is used for storing patch package + * related file object information and is similar to the pkg contents table. + * This table is for future use in allowing FS object checking instead or + * in lieu of package level chacking. + * + * The pkgs column is not normalized. This is due to trying to represent the + * data the same way as the legacy patch system does. The column should be + * normalized when the Patch Architecture is re-designed. + * + * Note that path is given the maximum length of /usr/include/limits.h + * defined PATH_MAX. + */ + +const char *pcPatchContents = { + "CREATE TABLE patch_contents_table (" \ + "patch CHAR(16) NOT NULL," \ + "path VARCHAR(1024) NOT NULL," \ + "ftype CHAR(1) NOT NULL," \ + "class CHAR(32) NOT NULL," \ + "mode CHAR(5) NOT NULL," \ + "owner CHAR(32) NOT NULL," \ + "grp CHAR(32) NOT NULL," \ + "major CHAR(32) NOT NULL," \ + "minor CHAR(32) NOT NULL," \ + "sz CHAR(32) NOT NULL," \ + "sum CHAR(32) NOT NULL," \ + "modtime CHAR(32) NOT NULL," \ + "pkgstatus CHAR(1) NOT NULL," \ + "pkgs VARCHAR(4096) NOT NULL," \ + "PRIMARY KEY(patch)" \ + "ON CONFLICT REPLACE);"}; + +/* + * The patch table. This table represents all patch meta data needed + * to handle dependency checking during the installation and removal of + * a patch. + */ + +const char *pcPatchPkg = { + "CREATE TABLE patch_pkg_table (" \ + "patch CHAR(16) NOT NULL," \ + "pkg VARCHAR(64) NOT NULL," \ + "PRIMARY KEY(patch, pkg)" \ + "ON CONFLICT REPLACE);"}; + +/* This table represents the informatin associated with an installed patch */ + +const char *pcPatch = { + "CREATE TABLE patch_table (" \ + "patch CHAR(16) NOT NULL," \ + "rev CHAR(8) NOT NULL," \ + "bcode CHAR(12) NOT NULL," \ + "sep CHAR(1) NOT NULL," \ + "obs VARCHAR(1024) NULL," \ + "reqs VARCHAR(1024) NULL," \ + "incs VARCHAR(1024) NULL," \ + "backout VARCHAR(256) NULL," \ + "time TIMESTAMP(64) NOT NULL," \ + "pkgs VARCHAR(1024) NOT NULL," \ + "PRIMARY KEY(patch)" \ + "ON CONFLICT REPLACE);"}; + +/* This table represents the patchinfo file in its entirety */ + +const char *pcPatchinfo = { + "CREATE TABLE patchinfo_table (" \ + "patch CHAR(16) NOT NULL," \ + "key CHAR(256) NOT NULL," \ + "value CHAR(256) NOT NULL," \ + "PRIMARY KEY(patch)" \ + "ON CONFLICT REPLACE);"}; + +/* + * Since it is read in as a whole - column by column + * and processed by legacy code which expects all of + * the data, all the time, it is not useful to separate + * the columns into separate tables via normalization. + * In effect, each column is logically one data unit, + * even if it comprises several distinct values (ie. pkgs). + * + * Note that path is given the maximum length of /usr/include/limits.h + * defined PATH_MAX. + */ + +const char *pcContents = + "CREATE TABLE pkg_table (" \ + "path VARCHAR(1024) NOT NULL," \ + "ftype CHAR(1) NOT NULL," \ + "class CHAR(32) NOT NULL," \ + "mode CHAR(5) DEFAULT '-1'," \ + "owner CHAR(32) DEFAULT '?'," \ + "grp CHAR(32) DEFAULT '?'," \ + "major CHAR(32) DEFAULT '-1'," \ + "minor CHAR(32) DEFAULT '-1'," \ + "sz CHAR(32) DEFAULT '-1'," \ + "sum CHAR(32) DEFAULT '-1'," \ + "modtime CHAR(32) DEFAULT '-1'," \ + "pkgstatus CHAR(1) DEFAULT '0'," \ + "pkgs VARCHAR(4096) NOT NULL," \ + "PRIMARY KEY(path)" \ + " ON CONFLICT REPLACE);"; + +/* + * It is necessary to save off the dependency comments so that + * they can be regenerated in the depend output. This is due + * to there being copyright information in the depend files + * which cannot be tossed. This is a logically distinct table + * to the depend data since it includes none of the other data + * used in that table. + */ + +const char *pcDepComments = + "CREATE TABLE depcomment_table (" \ + "pkg VARCHAR(64) NOT NULL, " \ + "comment VARCHAR(800) NOT NULL, " \ + "seqno INT NOT NULL, " \ + "PRIMARY KEY(pkg) " \ + "ON CONFLICT REPLACE);"; + +/* This is the table which contains the pkginfo data. */ + +const char *pcSQL_pkginfo = + "CREATE TABLE pkginfo_table (" \ + "pkg VARCHAR(80) NOT NULL, " \ + "param VARCHAR(128) NOT NULL, " \ + "value VARCHAR(128) NOT NULL, "\ + "seqno INT NOT NULL, " \ + "PRIMARY KEY(pkg, param) " \ + "ON CONFLICT REPLACE);"; + +/* + * This is the table which contains the depenency data. + * Note that the 'name' data is optional. Further, note + * that platform information (version & architecture) + * have been separated off into a separate table. + */ + +const char *pcSQL_depend = + "CREATE TABLE depend_table (" \ + "pkg VARCHAR(64) NOT NULL, " \ + "pkgdep VARCHAR(64) NOT NULL, " \ + "type CHAR(1) NOT NULL, "\ + "name VARCHAR(128) NULL, " \ + "seqno INT NOT NULL, " \ + "PRIMARY KEY(pkg, pkgdep) " \ + "ON CONFLICT REPLACE);"; + +/* + * This table includes data which is specifically related to + * an individual dependency. The version and arch info are + * related to a specific pkg/pkgdep pair. It is possible to + * list more than one version/arch pair for a pkg/pkgdep + * pair using the definition below. + */ + +const char *pcSQL_platform = + "CREATE TABLE depplatform_table ( " \ + "pkg VARCHAR(64) NOT NULL " \ + " REFERENCES depend_table(pkg), " \ + "pkgdep VARCHAR(64) NOT NULL " \ + " REFERENCES depend_table(pkgdep), " \ + "version VARCHAR(32) NOT NULL, " \ + "arch VARCHAR(32) NOT NULL, " \ + "seqno INT NOT NULL, " \ + "PRIMARY KEY (pkg, pkgdep, version, arch) " \ + "ON CONFLICT REPLACE);"; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _DBTABLES_H */ diff --git a/usr/src/lib/libpkg/common/devtype.c b/usr/src/lib/libpkg/common/devtype.c new file mode 100644 index 0000000000..e087966f86 --- /dev/null +++ b/usr/src/lib/libpkg/common/devtype.c @@ -0,0 +1,115 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include "pkgdev.h" +#include "pkglib.h" + +extern char *devattr(char *device, char *attribute); /* libadm.a */ + +int +devtype(char *alias, struct pkgdev *devp) +{ + char *name; + devp->mntflg = 0; + devp->name = alias; + devp->dirname = devp->pathname = devp->mount = NULL; + devp->fstyp = devp->cdevice = devp->bdevice = devp->norewind = NULL; + devp->rdonly = 0; + devp->capacity = 0; + + /* see if alias represents an existing file */ + if (alias[0] == '/') { + if (!isdir(alias)) { + devp->dirname = devp->name; + return (0); + } + } + + /* see if alias represents a mountable device (e.g., a floppy) */ + if ((devp->mount = devattr(alias, "mountpt")) != NULL && + devp->mount[0] != NULL) { + devp->bdevice = devattr(alias, "bdevice"); + if (!devp->bdevice || !devp->bdevice[0]) { + if (devp->bdevice) { + free(devp->bdevice); + devp->bdevice = NULL; + } + return (-1); + } + devp->dirname = devp->mount; + } else if (devp->mount) { + free(devp->mount); + devp->mount = NULL; + } + + devp->cdevice = devattr(alias, "cdevice"); + if (devp->cdevice && devp->cdevice[0]) { + /* check for capacity */ + if (name = devattr(alias, "capacity")) { + if (name[0]) + devp->capacity = atoll(name); + free(name); + } + /* check for norewind device */ + devp->norewind = devattr(alias, "norewind"); + if (devp->norewind && !devp->norewind[0]) { + free(devp->norewind); + devp->norewind = NULL; + } + + /* mountable devices will always have associated raw device */ + return (0); + } + if (devp->cdevice) { + free(devp->cdevice); + devp->cdevice = NULL; + } + /* + * if it is not a raw device, it must be a directory or a regular file + */ + name = devattr(alias, "pathname"); + if (!name || !name[0]) { + /* Assume a regular file */ + if (name) + free(name); + devp->pathname = alias; + return (0); + } + if (!isdir(name)) + devp->dirname = name; + else + devp->pathname = name; + return (0); +} diff --git a/usr/src/lib/libpkg/common/dstream.c b/usr/src/lib/libpkg/common/dstream.c new file mode 100644 index 0000000000..68ff540393 --- /dev/null +++ b/usr/src/lib/libpkg/common/dstream.c @@ -0,0 +1,1036 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/sysmacros.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <fcntl.h> +#ifdef u3b2 +#include <sys/sys3b.h> +#endif /* u3b2 */ +#include <openssl/err.h> +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" +#ifdef u3b2 +static +struct stat orig_st_buf; /* Stat structure of original file (3B2/CTC) */ +static char ds_ctcflg; +#endif /* u3b2 */ + +/* libadm.a */ +extern char *devattr(char *device, char *attribute); +extern int pkgnmchk(register char *pkg, register char *spec, + int presvr4flg); +extern int getvol(char *device, char *label, int options, char *prompt); + +#define CMDSIZ 512 +#define LSIZE 128 +#define DDPROC "/usr/bin/dd" +#define CPIOPROC "/usr/bin/cpio" + +/* device types */ + +#define G_TM_TAPE 1 /* Tapemaster controller */ +#define G_XY_DISK 3 /* xy disks */ +#define G_SD_DISK 7 /* scsi sd disk */ +#define G_XT_TAPE 8 /* xt tapes */ +#define G_SF_FLOPPY 9 /* sf floppy */ +#define G_XD_DISK 10 /* xd disks */ +#define G_ST_TAPE 11 /* scsi tape */ +#define G_NS 12 /* noswap pseudo-dev */ +#define G_RAM 13 /* ram pseudo-dev */ +#define G_FT 14 /* tftp */ +#define G_HD 15 /* 386 network disk */ +#define G_FD 16 /* 386 AT disk */ +#define G_FILE 28 /* file, not a device */ +#define G_NO_DEV 29 /* device does not require special treatment */ +#define G_DEV_MAX 30 /* last valid device type */ + +struct dstoc { + int cnt; + char pkg[NON_ABI_NAMELNGTH]; + int nparts; + long maxsiz; + char volnos[128]; + struct dstoc *next; +} *ds_head, *ds_toc; + +#define ds_nparts ds_toc->nparts +#define ds_maxsiz ds_toc->maxsiz + +int ds_totread; /* total number of parts read */ +int ds_fd = -1; +int ds_curpartcnt = -1; + +int ds_next(char *device, char *instdir); +int ds_ginit(char *device); +int ds_close(int pkgendflg); + +static FILE *ds_pp; +static int ds_realfd = -1; /* file descriptor for real device */ +static int ds_read; /* number of parts read for current package */ +static int ds_volno; /* volume number of current volume */ +static int ds_volcnt; /* total number of volumes */ +static char ds_volnos[128]; /* parts/volume info */ +static char *ds_device; +static int ds_volpart; /* number of parts read in current volume, */ + /* including skipped parts */ +static int ds_bufsize; +static int ds_skippart; /* number of parts skipped in current volume */ + +static int ds_getnextvol(char *device); +static int ds_skip(char *device, int nskip); + +void +ds_order(char *list[]) +{ + struct dstoc *toc_pt; + register int j, n; + char *pt; + + toc_pt = ds_head; + n = 0; + while (toc_pt) { + for (j = n; list[j]; j++) { + if (strcmp(list[j], toc_pt->pkg) == 0) { + /* just swap places in the array */ + pt = list[n]; + list[n++] = list[j]; + list[j] = pt; + } + } + toc_pt = toc_pt->next; + } +} + +static char *pds_header; +static char *ds_header; +static char *ds_header_raw; +static int ds_headsize; + +static char * +ds_gets(char *buf, int size) +{ + int length; + char *nextp; + + nextp = strchr(pds_header, '\n'); + if (nextp == NULL) { + length = strlen(pds_header); + if (length > size) + return (0); + if ((ds_header = (char *)realloc(ds_header, + ds_headsize + BLK_SIZE)) == NULL) + return (0); + if (read(ds_fd, ds_header + ds_headsize, BLK_SIZE) < BLK_SIZE) + return (0); + ds_headsize += BLK_SIZE; + nextp = strchr(pds_header, '\n'); + if (nextp == NULL) + return (0); + *nextp = '\0'; + if (length + (int)strlen(pds_header) > size) + return (0); + (void) strncpy(buf + length, pds_header, strlen(pds_header)); + buf[length + strlen(pds_header)] = '\0'; + pds_header = nextp + 1; + return (buf); + } + *nextp = '\0'; + if ((int)strlen(pds_header) > size) + return (0); + (void) strncpy(buf, pds_header, strlen(pds_header)); + buf[strlen(pds_header)] = '\0'; + pds_header = nextp + 1; + return (buf); +} + +/* + * function to determine if media is datastream or mounted + * floppy + */ +int +ds_readbuf(char *device) +{ + char buf[BLK_SIZE]; + + if (ds_fd >= 0) + (void) close(ds_fd); + if ((ds_fd = open(device, O_RDONLY)) >= 0 && + read(ds_fd, buf, BLK_SIZE) == BLK_SIZE && + strncmp(buf, HDR_PREFIX, 20) == 0) { + if ((ds_header = (char *)calloc(BLK_SIZE, 1)) == NULL) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_MEM)); + (void) ds_close(0); + return (0); + } + memcpy(ds_header, buf, BLK_SIZE); + ds_headsize = BLK_SIZE; + + if (ds_ginit(device) < 0) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_OPEN), device, errno); + (void) ds_close(0); + return (0); + } + return (1); + } else if (ds_fd >= 0) { + (void) close(ds_fd); + ds_fd = -1; + } + return (0); +} + +/* + * Determine how many additional volumes are needed for current package. + * Note: a 0 will occur as first volume number when the package begins + * on the next volume. + */ +static int +ds_volsum(struct dstoc *toc) +{ + int curpartcnt, volcnt; + char volnos[128], tmpvol[128]; + if (toc->volnos[0]) { + int index, sum; + sscanf(toc->volnos, "%d %[ 0-9]", &curpartcnt, volnos); + volcnt = 0; + sum = curpartcnt; + while (sum < toc->nparts && sscanf(volnos, "%d %[ 0-9]", + &index, tmpvol) >= 1) { + (void) strcpy(volnos, tmpvol); + volcnt++; + sum += index; + } + /* side effect - set number of parts read on current volume */ + ds_volpart = index; + return (volcnt); + } + ds_volpart += toc->nparts; + return (0); +} + +/* initialize ds_curpartcnt and ds_volnos */ +static void +ds_pkginit(void) +{ + if (ds_toc->volnos[0]) + sscanf(ds_toc->volnos, "%d %[ 0-9]", &ds_curpartcnt, ds_volnos); + else + ds_curpartcnt = -1; +} + +/* + * functions to pass current package info to exec'ed program + */ +void +ds_putinfo(char *buf) +{ + (void) sprintf(buf, "%d %d %d %d %d %d %d %d %d %d %s", + ds_fd, ds_realfd, ds_volcnt, ds_volno, ds_totread, ds_volpart, + ds_skippart, ds_bufsize, ds_toc->nparts, ds_toc->maxsiz, + ds_toc->volnos); +} + +int +ds_getinfo(char *string) +{ + ds_toc = (struct dstoc *)calloc(1, sizeof (struct dstoc)); + (void) sscanf(string, "%d %d %d %d %d %d %d %d %d %d %[ 0-9]", + &ds_fd, &ds_realfd, &ds_volcnt, &ds_volno, &ds_totread, + &ds_volpart, &ds_skippart, &ds_bufsize, &ds_toc->nparts, + &ds_toc->maxsiz, ds_toc->volnos); + ds_pkginit(); + return (ds_toc->nparts); +} + +/* + * Return true if the file descriptor (ds_fd) is open on the package stream. + */ +boolean_t +ds_fd_open(void) +{ + return (ds_fd >= 0 ? B_TRUE : B_FALSE); +} + +/* + * Read the source device. Acquire the header data and check it for validity. + */ +int +ds_init(char *device, char **pkg, char *norewind) +{ + struct dstoc *tail, *toc_pt; + char *ret; + char cmd[CMDSIZ]; + char line[LSIZE+1]; + int i, n, count = 0, header_size = BLK_SIZE; + + if (!ds_header) { /* If the header hasn't been read yet */ + if (ds_fd >= 0) + (void) ds_close(0); + + /* always start with rewind device */ + if ((ds_fd = open(device, O_RDONLY)) < 0) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_OPEN), device, errno); + return (-1); + } + + /* allocate room for the header equivalent to a block */ + if ((ds_header = (char *)calloc(BLK_SIZE, 1)) == NULL) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_MEM)); + return (-1); + } + + /* initialize the device */ + if (ds_ginit(device) < 0) { + (void) ds_close(0); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_OPEN), device, errno); + return (-1); + } + + /* read a logical block from the source device */ + if (read(ds_fd, ds_header, BLK_SIZE) != BLK_SIZE) { + rpterr(); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_TOC)); + (void) ds_close(0); + return (-1); + } + + /* + * This loop scans the medium for the start of the header. + * If the above read worked, we skip this. If it did't, this + * loop will retry the read ten times looking for the header + * marker string. + */ + while (strncmp(ds_header, HDR_PREFIX, 20) != 0) { + /* only ten tries iff the device rewinds */ + if (!norewind || count++ > 10) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_TOC)); + (void) ds_close(0); + return (-1); + } + + /* read through to the last block */ + if (count > 1) + while (read(ds_fd, ds_header, BLK_SIZE) > 0) + ; + + /* then close the device */ + (void) ds_close(0); + + /* and reopen it */ + if ((ds_fd = open(norewind, O_RDONLY)) < 0) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_OPEN), device, errno); + (void) free(ds_header); + return (-1); + } + + /* initialize the device */ + if (ds_ginit(device) < 0) { + (void) ds_close(0); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_OPEN), device, errno); + return (-1); + } + + /* read the block again */ + if (read(ds_fd, ds_header, BLK_SIZE) != BLK_SIZE) { + rpterr(); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_TOC)); + (void) ds_close(0); + return (-1); + } + } + + /* Now keep scanning until the whole header is in place. */ + while (strstr(ds_header, HDR_SUFFIX) == NULL) { + /* We need a bigger buffer */ + if ((ds_header = (char *)realloc(ds_header, + header_size + BLK_SIZE)) == NULL) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_MEM)); + (void) ds_close(0); + return (1); + } + + /* clear the new memory */ + (void) memset(ds_header + header_size, '\0', + BLK_SIZE); + + + /* read a logical block from the source device */ + if (read(ds_fd, ds_header + header_size, BLK_SIZE) != + BLK_SIZE) { + rpterr(); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_TOC)); + (void) ds_close(0); + return (-1); + } else + header_size += BLK_SIZE; /* new size */ + } + + /* + * remember rewind device for ds_close to rewind at + * close + */ + if (count >= 1) + ds_device = device; + ds_headsize = header_size; + + } + + pds_header = ds_header; + + /* save raw copy of header for later use in BIO_dump_header */ + if ((ds_header_raw = (char *)malloc(header_size)) == NULL) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_MEM)); + (void) ds_close(0); + return (1); + } + memcpy(ds_header_raw, ds_header, header_size); + + /* read datastream table of contents */ + ds_head = tail = (struct dstoc *)0; + ds_volcnt = 1; + + while (ret = ds_gets(line, LSIZE)) { + if (strcmp(line, HDR_SUFFIX) == 0) + break; + if (!line[0] || line[0] == '#') + continue; + toc_pt = (struct dstoc *)calloc(1, sizeof (struct dstoc)); + if (!toc_pt) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_MEM)); + ecleanup(); + (void) free(ds_header); + return (-1); + } + if (sscanf(line, "%s %d %d %[ 0-9]", toc_pt->pkg, + &toc_pt->nparts, &toc_pt->maxsiz, toc_pt->volnos) < 3) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_TOC)); + free(toc_pt); + (void) free(ds_header); + ecleanup(); + return (-1); + } + if (tail) { + tail->next = toc_pt; + tail = toc_pt; + } else + ds_head = tail = toc_pt; + ds_volcnt += ds_volsum(toc_pt); + } + if (!ret) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_TOC)); + (void) free(ds_header); + return (-1); + } + sighold(SIGINT); + sigrelse(SIGINT); + if (!ds_head) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_EMPTY)); + (void) free(ds_header); + return (-1); + } + /* this could break, thanks to cpio command limit */ +#ifndef SUNOS41 + (void) sprintf(cmd, "%s -icdumD -C %d", CPIOPROC, (int)BLK_SIZE); +#else + (void) sprintf(cmd, "%s -icdum -C %d", CPIOPROC, (int)BLK_SIZE); +#endif + n = 0; + for (i = 0; pkg[i]; i++) { + if (strcmp(pkg[i], "all") == 0) + continue; + if (n == 0) { + strcat(cmd, " "); + n = 1; + } + strlcat(cmd, pkg[i], CMDSIZ); + strlcat(cmd, "'/*' ", CMDSIZ); + + /* extract signature too, if present. */ + strlcat(cmd, SIGNATURE_FILENAME, CMDSIZ); + strlcat(cmd, " ", CMDSIZ); + } + + /* + * if we are extracting all packages (pkgs == NULL), + * signature will automatically be extracted + */ + if (n = esystem(cmd, ds_fd, -1)) { + rpterr(); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_CMDFAIL), cmd, n); + (void) free(ds_header); + return (-1); + } + + ds_toc = ds_head; + ds_totread = 0; + ds_volno = 1; + return (0); +} + +int +ds_findpkg(char *device, char *pkg) +{ + char *pkglist[2]; + int nskip, ods_volpart; + + if (ds_head == NULL) { + pkglist[0] = pkg; + pkglist[1] = NULL; + if (ds_init(device, pkglist, NULL)) + return (-1); + } + + if (!pkg || pkgnmchk(pkg, "all", 0)) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_PKGNAME)); + return (-1); + } + + nskip = 0; + ds_volno = 1; + ds_volpart = 0; + ds_toc = ds_head; + while (ds_toc) { + if (strcmp(ds_toc->pkg, pkg) == 0) + break; + nskip += ds_toc->nparts; + ds_volno += ds_volsum(ds_toc); + ds_toc = ds_toc->next; + } + if (!ds_toc) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_NOPKG), pkg); + return (-1); + } + + ds_pkginit(); + ds_skippart = 0; + if (ds_curpartcnt > 0) { + ods_volpart = ds_volpart; + /* + * skip past archives belonging to last package on current + * volume + */ + if (ds_volpart > 0 && ds_getnextvol(device)) + return (-1); + ds_totread = nskip - ods_volpart; + if (ds_skip(device, ods_volpart)) + return (-1); + } else if (ds_curpartcnt < 0) { + if (ds_skip(device, nskip - ds_totread)) + return (-1); + } else + ds_totread = nskip; + ds_read = 0; + return (ds_nparts); +} + +/* + * Get datastream part + * Call for first part should be preceded by + * call to ds_findpkg + */ + +int +ds_getpkg(char *device, int n, char *dstdir) +{ + struct statvfs64 svfsb; + u_longlong_t free_blocks; + + if (ds_read >= ds_nparts) + return (2); + + if (ds_read == n) + return (0); + else if ((ds_read > n) || (n > ds_nparts)) + return (2); + + if (ds_maxsiz > 0) { + if (statvfs64(".", &svfsb)) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_STATFS), errno); + return (-1); + } +#ifdef SUNOS41 + free_blocks = svfsb.f_bfree * howmany(svfsb.f_bsize, DEV_BSIZE); +#else /* !SUNOS41 */ + free_blocks = (((long)svfsb.f_frsize > 0) ? + howmany(svfsb.f_frsize, DEV_BSIZE) : + howmany(svfsb.f_bsize, DEV_BSIZE)) * svfsb.f_bfree; +#endif /* SUNOS41 */ + if ((ds_maxsiz + 50) > free_blocks) { + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_NOSPACE), ds_maxsiz+50, free_blocks); + return (-1); + } + } + return (ds_next(device, dstdir)); +} + +static int +ds_getnextvol(char *device) +{ + char prompt[128]; + int n; + + if (ds_close(0)) + return (-1); + (void) sprintf(prompt, + pkg_gt("Insert %%v %d of %d into %%p"), + ds_volno, ds_volcnt); + if (n = getvol(device, NULL, NULL, prompt)) + return (n); + if ((ds_fd = open(device, O_RDONLY)) < 0) + return (-1); + if (ds_ginit(device) < 0) { + (void) ds_close(0); + return (-1); + } + ds_volpart = 0; + return (0); +} + +/* + * called by ds_findpkg to skip past archives for unwanted packages + * in current volume + */ +static int +ds_skip(char *device, int nskip) +{ + char cmd[CMDSIZ]; + int n, onskip = nskip; + + while (nskip--) { + /* skip this one */ +#ifndef SUNOS41 + (void) sprintf(cmd, "%s -ictD -C %d > /dev/null", +#else + (void) sprintf(cmd, "%s -ict -C %d > /dev/null", +#endif + CPIOPROC, (int)BLK_SIZE); + if (n = esystem(cmd, ds_fd, -1)) { + rpterr(); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_CMDFAIL), cmd, n); + nskip = onskip; + if (ds_volno == 1 || ds_volpart > 0) + return (n); + if (n = ds_getnextvol(device)) + return (n); + } + } + ds_totread += onskip; + ds_volpart = onskip; + ds_skippart = onskip; + return (0); +} + +/* skip to end of package if necessary */ +void +ds_skiptoend(char *device) +{ + if (ds_read < ds_nparts && ds_curpartcnt < 0) + (void) ds_skip(device, ds_nparts - ds_read); +} + +int +ds_next(char *device, char *instdir) +{ + char cmd[CMDSIZ], tmpvol[128]; + int nparts, n, index; + + /*CONSTCOND*/ + while (1) { + if (ds_read + 1 > ds_curpartcnt && ds_curpartcnt >= 0) { + ds_volno++; + if (n = ds_getnextvol(device)) + return (n); + (void) sscanf(ds_volnos, "%d %[ 0-9]", &index, tmpvol); + (void) strcpy(ds_volnos, tmpvol); + ds_curpartcnt += index; + } +#ifndef SUNOS41 + (void) sprintf(cmd, "%s -icdumD -C %d", +#else + (void) sprintf(cmd, "%s -icdum -C %d", +#endif + CPIOPROC, (int)BLK_SIZE); + if (n = esystem(cmd, ds_fd, -1)) { + rpterr(); + progerr(pkg_gt(ERR_UNPACK)); + logerr(pkg_gt(MSG_CMDFAIL), cmd, n); + } + if (ds_read == 0) + nparts = 0; + else + nparts = ds_toc->nparts; + if (n || (n = ckvolseq(instdir, ds_read + 1, nparts))) { + if (ds_volno == 1 || ds_volpart > ds_skippart) + return (-1); + + if (n = ds_getnextvol(device)) + return (n); + continue; + } + ds_read++; + ds_totread++; + ds_volpart++; + + return (0); + } + /*NOTREACHED*/ +} + +/* + * Name: BIO_ds_dump + * Description: Dumps all data from the static 'ds_fd' file handle into + * the supplied BIO. + * + * Arguments: err - where to record any errors. + * device - Description of device being dumped into, + * for error reporting + * bio - BIO object to dump data into + * + * Returns : zero - successfully dumped all data to EOF + * non-zero - some failure occurred. + */ +int +BIO_ds_dump(PKG_ERR *err, char *device, BIO *bio) +{ + int amtread; + char readbuf[BLK_SIZE]; + + /* + * note this will read to the end of the device, so it won't + * work for character devices since we don't know when the + * end of the CPIO archive is + */ + while ((amtread = read(ds_fd, readbuf, BLK_SIZE)) != 0) { + if (BIO_write(bio, readbuf, amtread) != amtread) { + pkgerr_add(err, PKGERR_WRITE, ERR_WRITE, device, + ERR_error_string(ERR_get_error(), NULL)); + return (1); + } + } + + return (0); + /*NOTREACHED*/ +} + + +/* + * Name: BIO_ds_dump_header + * Description: Dumps all ds_headsize bytes from the + * static 'ds_header_raw' character array + * to the supplied BIO. + * + * Arguments: err - where to record any errors. + * bio - BIO object to dump data into + * + * Returns : zero - successfully dumped all raw + * header characters + * non-zero - some failure occurred. + */ +int +BIO_ds_dump_header(PKG_ERR *err, BIO *bio) +{ + + char zeros[BLK_SIZE]; + + memset(zeros, 0, BLK_SIZE); + + if (BIO_write(bio, ds_header_raw, ds_headsize) != ds_headsize) { + pkgerr_add(err, PKGERR_WRITE, ERR_WRITE, "bio", + ERR_error_string(ERR_get_error(), NULL)); + return (1); + } + + return (0); +} + +/* + * ds_ginit: Determine the device being accessed, set the buffer size, + * and perform any device specific initialization. For the 3B2, + * a device with major number of 17 (0x11) is an internal hard disk, + * unless the minor number is 128 (0x80) in which case it is an internal + * floppy disk. Otherwise, get the system configuration + * table and check it by comparing slot numbers to major numbers. + * For the special case of the 3B2 CTC several unusual things must be done. + * To enable + * streaming mode on the CTC, the file descriptor must be closed, re-opened + * (with O_RDWR and O_CTSPECIAL flags set), the STREAMON ioctl(2) command + * issued, and the file descriptor re-re-opened either read-only or write_only. + */ + +int +ds_ginit(char *device) +{ +#ifdef u3b2 + major_t maj; + minor_t min; + int nflag, i, count, size; + struct s3bconf *buffer; + struct s3bc *table; + struct stat st_buf; + int devtype; + char buf[BLK_SIZE]; + int fd2, fd; +#endif /* u3b2 */ + int oflag; + char *pbufsize, cmd[CMDSIZ]; + int fd2, fd; + + if ((pbufsize = devattr(device, "bufsize")) != NULL) { + ds_bufsize = atoi(pbufsize); + (void) free(pbufsize); + } else + ds_bufsize = BLK_SIZE; + oflag = fcntl(ds_fd, F_GETFL, 0); +#ifdef u3b2 + devtype = G_NO_DEV; + if (fstat(ds_fd, &st_buf) == -1) + return (-1); + if (!S_ISCHR(st_buf.st_mode) && !S_ISBLK(st_buf.st_mode)) + goto lab; + + /* + * We'll have to add a remote attribute to stat but this should + * work for now. + */ + else if (st_buf.st_dev & 0x8000) /* if remote rdev */ + goto lab; + + maj = major(st_buf.st_rdev); + min = minor(st_buf.st_rdev); + if (maj == 0x11) { /* internal hard or floppy disk */ + if (min & 0x80) + devtype = G_3B2_FD; /* internal floppy disk */ + else + devtype = G_3B2_HD; /* internal hard disk */ + } else { + if (sys3b(S3BCONF, (struct s3bconf *)&count, sizeof (count)) == + -1) + return (-1); + size = sizeof (int) + (count * sizeof (struct s3bconf)); + buffer = (struct s3bconf *)malloc((unsigned)size); + if (sys3b(S3BCONF, buffer, size) == -1) + return (-1); + table = (struct s3bc *)((char *)buffer + sizeof (int)); + for (i = 0; i < count; i++) { + if (maj == (int)table->board) { + if (strncmp(table->name, "CTC", 3) == 0) { + devtype = G_3B2_CTC; + break; + } else if (strncmp(table->name, "TAPE", 4) + == 0) { + devtype = G_TAPE; + break; + } + /* other possible devices can go here */ + } + table++; + } + } + switch (devtype) { + case G_3B2_CTC: /* do special CTC initialization */ + ds_bufsize = pbufsize ? ds_bufsize : 15872; + if (fstat(ds_fd, &orig_st_buf) < 0) { + ds_bufsize = -1; + break; + } + nflag = (O_RDWR | O_CTSPECIAL); + (void) close(ds_fd); + if ((ds_fd = open(device, nflag, 0666)) != -1) { + if (ioctl(ds_fd, STREAMON) != -1) { + (void) close(ds_fd); + nflag = (oflag == O_WRONLY) ? + O_WRONLY : O_RDONLY; + if ((ds_fd = + open(device, nflag, 0666)) == -1) { + rpterr(); + progerr( + pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), + device, errno); + return (-1); + } + ds_bufsize = 15872; + } + } else + ds_bufsize = -1; + if (oflag == O_RDONLY && ds_header && ds_totread == 0) + /* Have already read in first block of header */ + read(ds_fd, buf, BLK_SIZE); + ds_ctcflg = 1; + + break; + case G_NO_DEV: + case G_3B2_HD: + case G_3B2_FD: + case G_TAPE: + case G_SCSI_HD: /* not developed yet */ + case G_SCSI_FD: + case G_SCSI_9T: + case G_SCSI_Q24: + case G_SCSI_Q120: + case G_386_HD: + case G_386_FD: + case G_386_Q24: + ds_bufsize = pbufsize ? ds_bufsize : BLK_SIZE; + break; + default: + ds_bufsize = -1; + errno = ENODEV; + } /* devtype */ +lab: +#endif /* u3b2 */ + if (ds_bufsize > BLK_SIZE) { + if (oflag & O_WRONLY) + fd = 1; + else + fd = 0; + fd2 = fcntl(fd, F_DUPFD, fd); + (void) close(fd); + fcntl(ds_fd, F_DUPFD, fd); + if (fd) + sprintf(cmd, "%s obs=%d 2>/dev/null", DDPROC, + ds_bufsize); + else + sprintf(cmd, "%s ibs=%d 2>/dev/null", DDPROC, + ds_bufsize); + if ((ds_pp = popen(cmd, fd ? "w" : "r")) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_POPEN), cmd, errno); + return (-1); + } + (void) close(fd); + fcntl(fd2, F_DUPFD, fd); + (void) close(fd2); + ds_realfd = ds_fd; + ds_fd = fileno(ds_pp); + } + return (ds_bufsize); +} + +int +ds_close(int pkgendflg) +{ +#ifdef u3b2 + int cnt, mode; + char *ptr; + struct stat statbuf; +#endif /* u3b2 */ + int n, ret = 0; + +#ifdef u3b2 + if (ds_pp && ds_ctcflg) { + ds_ctcflg = 0; + if ((mode = fcntl(ds_realfd, F_GETFL, 0)) < 0) { + ret = -1; + } else if (mode & O_WRONLY) { + /* + * pipe to dd write process, + * make sure one more buffer + * gets written out + */ + if ((ptr = calloc(BLK_SIZE, 1)) == NULL) { + ret = -1; + /* pad to bufsize */ + } else { + cnt = ds_bufsize; + while (cnt > 0) { + if ((n = write(ds_fd, ptr, + BLK_SIZE)) < 0) { + ret = -1; + break; + } + cnt -= n; + } + (void) free(ptr); + } + } + } +#endif + if (pkgendflg) { + if (ds_header) + (void) free(ds_header); + ds_header = (char *)NULL; + ds_totread = 0; + } + + if (ds_pp) { + (void) pclose(ds_pp); + ds_pp = 0; + (void) close(ds_realfd); + ds_realfd = -1; + ds_fd = -1; + } else if (ds_fd >= 0) { + (void) close(ds_fd); + ds_fd = -1; + } + + if (ds_device) { + /* rewind device */ + if ((n = open(ds_device, 0)) >= 0) + (void) close(n); + ds_device = NULL; + } + return (ret); +} diff --git a/usr/src/lib/libpkg/common/fmkdir.c b/usr/src/lib/libpkg/common/fmkdir.c new file mode 100644 index 0000000000..a56933bc3d --- /dev/null +++ b/usr/src/lib/libpkg/common/fmkdir.c @@ -0,0 +1,65 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "pkglib.h" + +/* + * Name: fmkdir + * Description: force the creation of a directory, even if the current + * node exists and is not a directory + * Arguments: a_path - pointer to string representing the path to the + * directory to create + * a_mode - mode(2) bits to set the path to if created + * returns: 0 - directory created + * 1 - could not remove existing non-directory node + * 2 - could not create specified new directory + */ +int +fmkdir(char *a_path, int a_mode) +{ + if (access(a_path, F_OK) == 0) { + if (rrmdir(a_path) != 0) { + return (1); + } + } + + if (mkdir(a_path, a_mode) != 0) { + return (2); + } + + return (0); +} diff --git a/usr/src/lib/libpkg/common/gpkglist.c b/usr/src/lib/libpkg/common/gpkglist.c new file mode 100644 index 0000000000..9257c6afcb --- /dev/null +++ b/usr/src/lib/libpkg/common/gpkglist.c @@ -0,0 +1,359 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <stdlib.h> +#include <valtools.h> +#include "pkginfo.h" +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkgstrct.h" +#include "pkglocale.h" + +extern char *pkgdir; /* WHERE? */ + +/* libadm.a */ +extern CKMENU *allocmenu(char *label, int attr); +extern int ckitem(CKMENU *menup, char *item[], short max, char *defstr, + char *error, char *help, char *prompt); +extern int pkgnmchk(register char *pkg, register char *spec, + int presvr4flg); +extern int fpkginfo(struct pkginfo *info, char *pkginst); +extern char *fpkginst(char *pkg, ...); +extern int setinvis(CKMENU *menup, char *choice); +extern int setitem(CKMENU *menup, char *choice); + +#define CMDSIZ 512 +#define LSIZE 256 +#define MAXSIZE 128 +#define MALLOCSIZ 128 +#define MAX_CAT_ARGS 64 +#define MAX_CAT_LEN 16 + +static int cont_in_list = 0; /* live continuation */ +static char cont_keyword[PKGSIZ+1]; /* the continuation keyword */ + +/* + * Allocate memory for the next package name. This function attempts the + * allocation and if that succeeds, returns a pointer to the new memory + * location and increments "n". Otherwise, it returens NULL and n is + * unchanged. + */ +static char ** +next_n(int *n, char **nwpkg) +{ + int loc_n = *n; + + if ((++loc_n % MALLOCSIZ) == 0) { + nwpkg = (char **)realloc(nwpkg, + (loc_n+MALLOCSIZ) * sizeof (char **)); + if (nwpkg == NULL) { + progerr(pkg_gt(ERR_MEMORY), errno); + errno = ENOMEM; + return (NULL); + } + } + + *n = loc_n; + return (nwpkg); +} + +/* + * This informs gpkglist() to put a keyword at the head of the pkglist. This + * was originally intended for live continue, but it may have other + * applications as well. + */ +void +pkglist_cont(char *keyword) +{ + cont_in_list = 1; + (void) strncpy(cont_keyword, keyword, PKGSIZ); +} + +/* + * This function constructs the list of packages that the user wants managed. + * It may be a list on the command line, it may be some or all of the + * packages in a directory or it may be a continuation from a previous + * dryrun. It may also be a list of pkgs gathered from the CATEGORY parameter + * in a spooled or installed pkginfo file. + */ +char ** +gpkglist(char *dir, char **pkg, char **catg) +{ + struct _choice_ *chp; + struct pkginfo info; + char *inst; + CKMENU *menup; + char temp[LSIZE]; + char *savedir, **nwpkg; + int i, n; + + savedir = pkgdir; + pkgdir = dir; + + info.pkginst = NULL; /* initialize for memory handling */ + if (pkginfo(&info, "all", NULL, NULL)) { + errno = ENOPKG; /* contains no valid packages */ + pkgdir = savedir; + return (NULL); + } + + /* + * If no explicit list was provided and this is not a continuation + * (implying a certain level of direction on the caller's part) + * present a menu of available packages for installation. + */ + if (pkg[0] == NULL && !cont_in_list) { + menup = allocmenu(pkg_gt(HEADER), CKALPHA); + if (setinvis(menup, "all")) { + errno = EFAULT; + return (NULL); + } + do { + /* bug id 1087404 */ + if (!info.pkginst || !info.name || !info.arch || + !info.version) + continue; + (void) sprintf(temp, "%s %s\n(%s) %s", info.pkginst, + info.name, info.arch, info.version); + if (setitem(menup, temp)) { + errno = EFAULT; + return (NULL); + } + } while (pkginfo(&info, "all", NULL, NULL) == 0); + /* clear memory usage by pkginfo */ + (void) pkginfo(&info, NULL, NULL, NULL); + pkgdir = savedir; /* restore pkgdir to orig value */ + + nwpkg = (char **)calloc(MALLOCSIZ, sizeof (char **)); + n = ckitem(menup, nwpkg, MALLOCSIZ, "all", NULL, + pkg_gt(HELP), pkg_gt(PROMPT)); + if (n) { + free(nwpkg); + errno = ((n == 3) ? EINTR : EFAULT); + pkgdir = savedir; + return (NULL); + } + if (strcmp(nwpkg[0], "all") == 0) { + chp = menup->choice; + for (n = 0; chp; /* void */) { + nwpkg[n] = strdup(chp->token); + nwpkg = next_n(&n, nwpkg); + chp = chp->next; + nwpkg[n] = NULL; + } + } else { + for (n = 0; nwpkg[n]; n++) + nwpkg[n] = strdup(nwpkg[n]); + } + (void) setitem(menup, NULL); /* free resources */ + free(menup); + pkgdir = savedir; + return (nwpkg); + } + + /* clear memory usage by pkginfo */ + (void) pkginfo(&info, NULL, NULL, NULL); + + nwpkg = (char **)calloc(MALLOCSIZ, sizeof (char **)); + + /* + * pkg array contains the instance identifiers to + * be selected, or possibly wildcard definitions + */ + i = n = 0; + do { + if (cont_in_list) { /* This is a live continuation. */ + nwpkg[n] = strdup(cont_keyword); + nwpkg = next_n(&n, nwpkg); + nwpkg[n] = NULL; + cont_in_list = 0; /* handled */ + + if (pkg[0] == NULL) { /* It's just a continuation. */ + break; + } + } else if (pkgnmchk(pkg[i], "all", 1)) { + /* wildcard specification */ + (void) fpkginst(NULL); + inst = fpkginst(pkg[i], NULL, NULL); + if (inst == NULL) { + progerr(pkg_gt(ERR_NOPKG), pkg[i]); + free(nwpkg); + nwpkg = NULL; + errno = ESRCH; + break; + } + do { + if (catg != NULL) { + pkginfo(&info, inst, NULL, NULL); + if (!is_same_CATEGORY(catg, + info.catg)) + continue; + } + nwpkg[n] = strdup(inst); + nwpkg = next_n(&n, nwpkg); + nwpkg[n] = NULL; + } while (inst = fpkginst(pkg[i], NULL, NULL)); + } else { + if (fpkginfo(&info, pkg[i])) { + progerr(pkg_gt(ERR_NOPKG), pkg[i]); + free(nwpkg); + nwpkg = NULL; + errno = ESRCH; + break; + } + nwpkg[n] = strdup(pkg[i]); + nwpkg = next_n(&n, nwpkg); + nwpkg[n] = NULL; + } + } while (pkg[++i]); + + (void) fpkginst(NULL); + (void) fpkginfo(&info, NULL); + pkgdir = savedir; /* restore pkgdir to orig value */ + + if (catg != NULL) { + if (nwpkg[0] == NULL) { + + /* + * No pkgs in the spooled directory matched the + * category specified by the user. + */ + + free(nwpkg); + return (NULL); + } + } + return (nwpkg); +} + +/* + * Check category passed in on the command line to see if it is valid. + * + * returns 0 if the category is valid + * returns 1 if the category is invalid + */ + +int +is_not_valid_category(char **category, char *progname) +{ + if (strcasecmp(progname, "pkgrm") == 0) { + if (is_same_CATEGORY(category, "system")) + return (1); + } + + return (0); +} + +/* + * Check category length + * + * returns 0 if the category length is valid + * returns 1 if a category has length > 16 chars as defined by the SVr4 ABI + */ + +int +is_not_valid_length(char **category) +{ + int i; + + for (i = 0; category[i] != NULL; i++) { + if (strlen(category[i]) > MAX_CAT_LEN) + return (1); + } + + return (0); +} + +/* + * Check category passed in on the command line against the CATEGORY in the + * spooled or installed packages pkginfo file. + * + * returns 0 if categories match + * returns 1 if categories don't match + */ + +int +is_same_CATEGORY(char **category, char *persistent_category) +{ + int i, j, n = 0; + char *pers_catg, **pers_catgs; + + pers_catg = strdup(persistent_category); + + pers_catgs = (char **)calloc(MAX_CAT_LEN, sizeof (char **)); + + pers_catgs[n++] = strtok(pers_catg, " \t\n, "); + while (pers_catgs[n] = strtok(NULL, " \t\n, ")) + n++; + + for (i = 0; category[i] != NULL; i++) { + for (j = 0; j < n; j++) { + if (strcasecmp(category[i], pers_catgs[j]) == 0) { + return (1); + } + } + } + + return (0); +} + +/* + * Given a string of categories, construct a null-terminated array of + * categories. + * + * returns the array of categories or NULL + */ + +char ** +get_categories(char *catg_arg) +{ + int n = 0; + char *tmp_catg; + char **catgs; + + tmp_catg = strdup(catg_arg); + + catgs = (char **)calloc(MAX_CAT_LEN, sizeof (char **)); + + catgs[n++] = strtok(tmp_catg, " \t\n, "); + while (catgs[n] = strtok(NULL, " \t\n, ")) + n++; + + if (*catgs == NULL) + return (NULL); + else + return (catgs); +} diff --git a/usr/src/lib/libpkg/common/gpkgmap.c b/usr/src/lib/libpkg/common/gpkgmap.c new file mode 100644 index 0000000000..d94b16c4d1 --- /dev/null +++ b/usr/src/lib/libpkg/common/gpkgmap.c @@ -0,0 +1,1275 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include "pkgstrct.h" +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" + +#define ERR_CANT_READ_LCLPATH "unable to read local pathname" +#define ERR_BAD_VOLUME_NUMBER "bad volume number" +#define ERR_CANNOT_READ_PATHNAME_FIELD "unable to read pathname field" +#define ERR_CANNOT_READ_CONTENT_INFO "unable to read content info" +#define ERR_EXTRA_TOKENS_PRESENT "extra tokens on input line" +#define ERR_CANNOT_READ_CLASS_TOKEN "unable to read class token" +#define ERR_BAD_LINK_SPEC "missing or invalid link specification" +#define ERR_UNKNOWN_FTYPE "unknown ftype" +#define ERR_NO_LINKSOURCE "no link source specified" +#define ERR_CANNOT_READ_MM_DEVNUMS "unable to read major/minor "\ + "device numbers" +static int eatwhite(FILE *fp); +static int getend(FILE *fp); +static int getstr(FILE *fp, char *sep, int n, char *str); +static int getnum(FILE *fp, int base, long *d, long bad); +static int getlnum(FILE *fp, int base, fsblkcnt_t *d, long bad); +static int getvalmode(FILE *fp, mode_t *d, long bad, int map); + +static int getendvfp(char **cp); +static void findendvfp(char **cp); +static int getstrvfp(char **cp, char *sep, int n, char *str); +static int getvalmodevfp(char **cp, mode_t *d, long bad, int map); +int getnumvfp(char **cp, int base, long *d, long bad); +int getlnumvfp(char **cp, int base, fsblkcnt_t *d, long bad); + +static char mypath[PATH_MAX]; +static char mylocal[PATH_MAX]; +static int mapmode = MAPNONE; +static char *maptype = ""; +static mode_t d_mode = BADMODE; +static char *d_owner = BADOWNER; +static char *d_group = BADGROUP; + +/* + * These determine how gpkgmap() deals with mode, owner and group defaults. + * It is assumed that the owner and group arguments represent static fields + * which will persist until attrdefault() is called. + */ +void +attrpreset(int mode, char *owner, char *group) +{ + d_mode = mode; + d_owner = owner; + d_group = group; +} + +void +attrdefault() +{ + d_mode = NOMODE; + d_owner = NOOWNER; + d_group = NOGROUP; +} + +/* + * This determines how gpkgmap() deals with environment variables in the + * mode, owner and group. Path is evaluated at a higher level based upon + * other circumstances. + */ +void +setmapmode(int mode) +{ + if (mode >= 0 || mode <= 3) { + mapmode = mode; + if (mode == MAPBUILD) + maptype = " build"; + else if (mode == MAPINSTALL) + maptype = " install"; + else + maptype = ""; + } +} + +/* This is the external query interface for mapmode. */ +int +getmapmode(void) +{ + return (mapmode); +} + +/* + * Unpack the pkgmap or the contents file or whatever file is in that format. + * Based upon mapmode, environment parameters will be resolved for mode, + * owner and group. + */ + +int +gpkgmap(struct cfent *ept, FILE *fp) +{ + int c; + boolean_t first_char = B_TRUE; + + setErrstr(NULL); + ept->volno = 0; + ept->ftype = BADFTYPE; + (void) strcpy(ept->pkg_class, BADCLASS); + ept->pkg_class_idx = -1; + ept->path = NULL; + ept->ainfo.local = NULL; + /* default attributes were supplied, so don't reset */ + ept->ainfo.mode = d_mode; + (void) strcpy(ept->ainfo.owner, d_owner); + (void) strcpy(ept->ainfo.group, d_group); +#ifdef SUNOS41 + ept->ainfo.xmajor = BADMAJOR; + ept->ainfo.xminor = BADMINOR; +#else + ept->ainfo.major = BADMAJOR; + ept->ainfo.minor = BADMINOR; +#endif + ept->cinfo.cksum = ept->cinfo.modtime = ept->cinfo.size = (-1L); + + ept->npkgs = 0; + + if (!fp) + return (-1); +readline: + c = eatwhite(fp); + + /* + * If the first character is not a digit, we assume that the + * volume number is 1. + */ + if (first_char && !isdigit(c)) { + ept->volno = 1; + } + first_char = B_FALSE; + + switch (c) { + case EOF: + return (0); + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (ept->volno) { + setErrstr(pkg_gt(ERR_BAD_VOLUME_NUMBER)); + goto error; + } + do { + ept->volno = (ept->volno*10)+c-'0'; + c = getc(fp); + } while (isdigit(c)); + if (ept->volno == 0) + ept->volno = 1; + + goto readline; + + case ':': + case '#': + (void) getend(fp); + /*FALLTHRU*/ + case '\n': + /* + * Since we are going to scan the next line, + * we need to reset volume number and first_char. + */ + ept->volno = 0; + first_char = B_TRUE; + goto readline; + + case 'i': + ept->ftype = (char)c; + c = eatwhite(fp); + /*FALLTHRU*/ + case '.': + case '/': + (void) ungetc(c, fp); + + if (getstr(fp, "=", PATH_MAX, mypath)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_PATHNAME_FIELD)); + goto error; + } + ept->path = mypath; + c = getc(fp); + if (c == '=') { + if (getstr(fp, NULL, PATH_MAX, mylocal)) { + setErrstr(pkg_gt(ERR_CANT_READ_LCLPATH)); + goto error; + } + ept->ainfo.local = mylocal; + } else + (void) ungetc(c, fp); + + if (ept->ftype == 'i') { + /* content info might exist */ + if (!getlnum(fp, 10, (fsblkcnt_t *)&ept->cinfo.size, + BADCONT) && + (getnum(fp, 10, (long *)&ept->cinfo.cksum, + BADCONT) || + getnum(fp, 10, (long *)&ept->cinfo.modtime, + BADCONT))) { + setErrstr(pkg_gt(ERR_CANNOT_READ_CONTENT_INFO)); + goto error; + } + } + if (getend(fp)) { + setErrstr(pkg_gt(ERR_EXTRA_TOKENS_PRESENT)); + return (-1); + } + return (1); + + case '?': + case 'f': + case 'v': + case 'e': + case 'l': + case 's': + case 'p': + case 'c': + case 'b': + case 'd': + case 'x': + ept->ftype = (char)c; + if (getstr(fp, NULL, CLSSIZ, ept->pkg_class)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_CLASS_TOKEN)); + goto error; + } + if (getstr(fp, "=", PATH_MAX, mypath)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_PATHNAME_FIELD)); + goto error; + } + ept->path = mypath; + + c = getc(fp); + if (c == '=') { + /* local path */ + if (getstr(fp, NULL, PATH_MAX, mylocal)) { + if (ept->ftype == 's' || ept->ftype == 'l') { + setErrstr(pkg_gt(ERR_READLINK)); + } else { + setErrstr( + pkg_gt(ERR_CANT_READ_LCLPATH)); + } + goto error; + } + ept->ainfo.local = mylocal; + } else if (strchr("sl", ept->ftype)) { + if ((c != EOF) && (c != '\n')) + (void) getend(fp); + setErrstr(pkg_gt(ERR_BAD_LINK_SPEC)); + return (-1); + } else + (void) ungetc(c, fp); + break; + + default: + setErrstr(pkg_gt(ERR_UNKNOWN_FTYPE)); +error: + (void) getend(fp); + return (-1); + } + + if (strchr("sl", ept->ftype) && (ept->ainfo.local == NULL)) { + setErrstr(pkg_gt(ERR_NO_LINKSOURCE)); + goto error; + } + + if (strchr("cb", ept->ftype)) { +#ifdef SUNOS41 + ept->ainfo.xmajor = BADMAJOR; + ept->ainfo.xminor = BADMINOR; + if (getnum(fp, 10, (long *)&ept->ainfo.xmajor, BADMAJOR) || + getnum(fp, 10, (long *)&ept->ainfo.xminor, BADMINOR)) +#else + ept->ainfo.major = BADMAJOR; + ept->ainfo.minor = BADMINOR; + if (getnum(fp, 10, (long *)&ept->ainfo.major, BADMAJOR) || + getnum(fp, 10, (long *)&ept->ainfo.minor, BADMINOR)) +#endif + { + setErrstr(pkg_gt(ERR_CANNOT_READ_MM_DEVNUMS)); + goto error; + } + } + + /* + * Links and information files don't have attributes associated with + * them. The following either resolves potential variables or passes + * them through. Mode is tested for validity to some degree. BAD??? + * is returned to indicate that no meaningful mode was provided. A + * higher authority will decide if that's OK or not. CUR??? means that + * the prototype file specifically requires a wildcard ('?') for + * that entry. We issue an error if attributes were entered wrong. + * We just return BAD??? if there was no entry at all. + */ + if (strchr("cbdxpfve", ept->ftype)) { + int retval; + + if ((retval = getvalmode(fp, &(ept->ainfo.mode), CURMODE, + (mapmode != MAPNONE))) == 1) + goto end; /* nothing else on the line */ + else if (retval == 2) + goto error; /* mode is too no good */ + + /* owner & group should be here */ + if ((retval = getstr(fp, NULL, ATRSIZ, + ept->ainfo.owner)) == 1) + goto end; /* no owner or group - warning */ + if (retval == -1) { + setErrstr(pkg_gt(ERR_OWNTOOLONG)); + goto error; + } + + if ((retval = getstr(fp, NULL, ATRSIZ, + ept->ainfo.group)) == 1) + goto end; /* no group - warning */ + if (retval == -1) { + setErrstr(pkg_gt(ERR_GRPTOOLONG)); + goto error; + } + + /* Resolve the parameters if required. */ + if (mapmode != MAPNONE) { + if (mapvar(mapmode, ept->ainfo.owner)) { + (void) snprintf(getErrbufAddr(), + getErrbufSize(), + pkg_gt(ERR_NOVAR), + maptype, ept->ainfo.owner); + setErrstr(getErrbufAddr()); + goto error; + } + if (mapvar(mapmode, ept->ainfo.group)) { + (void) snprintf(getErrbufAddr(), + getErrbufSize(), pkg_gt(ERR_NOVAR), + maptype, ept->ainfo.group); + setErrstr(getErrbufAddr()); + goto error; + } + } + } + + if (strchr("ifve", ept->ftype)) { + /* look for content description */ + if (!getlnum(fp, 10, (fsblkcnt_t *)&ept->cinfo.size, BADCONT) && + (getnum(fp, 10, (long *)&ept->cinfo.cksum, BADCONT) || + getnum(fp, 10, (long *)&ept->cinfo.modtime, BADCONT))) { + setErrstr(pkg_gt(ERR_CANNOT_READ_CONTENT_INFO)); + goto error; + } + } + + if (ept->ftype == 'i') + goto end; + +end: + if (getend(fp) && ept->pinfo) { + setErrstr(pkg_gt(ERR_EXTRA_TOKENS_PRESENT)); + return (-1); + } + +done: + return (1); +} + +/* + * Get and validate the mode attribute. This returns an error if + * 1. the mode string is too long + * 2. the mode string includes alpha characters + * 3. the mode string is not octal + * 4. mode string is an install parameter + * 5. mode is an unresolved build parameter and MAPBUILD is + * in effect. + * If the mode is a build parameter, it is + * 1. returned as is if MAPNONE is in effect + * 2. evaluated if MAPBUILD is in effect + * + * NOTE : We use "mapmode!=MAPBUILD" to gather that it is install + * time. At install time we just fix a mode with bad bits set by + * setting it to CURMODE. This should be an error in a few releases + * (2.8 maybe) but faulty modes are so common in existing packages + * that this is a reasonable exception. -- JST 1994-11-9 + * + * RETURNS + * 0 if mode is being returned as a valid value + * 1 if no attributes are present on the line + * 2 if there was a fundamental error + */ +static int +getvalmode(FILE *fp, mode_t *d, long bad, int map) +{ + char tempmode[20]; + mode_t tempmode_t; + int retval; + + if ((retval = getstr(fp, NULL, ATRSIZ, tempmode)) == 1) + return (1); + else if (retval == -1) { + setErrstr(pkg_gt(ERR_MODELONG)); + return (2); + } else { + /* + * If it isn't a '?' (meaning go with whatever mode is + * there), validate the mode and convert it to a mode_t. The + * "bad" variable here is a misnomer. It doesn't necessarily + * mean bad. + */ + if (tempmode[0] == '?') { + *d = WILDCARD; + } else { + /* + * Mode may not be an install parameter or a + * non-build parameter. + */ + if (tempmode[0] == '$' && + (isupper(tempmode[1]) || !islower(tempmode[1]))) { + setErrstr(pkg_gt(ERR_IMODE)); + return (2); + } + + if ((map) && (mapvar(mapmode, tempmode))) { + (void) snprintf(getErrbufAddr(), + getErrbufSize(), + pkg_gt(ERR_NOVAR), + maptype, tempmode); + setErrstr(getErrbufAddr()); + return (2); + } + + + if (tempmode[0] == '$') { + *d = BADMODE; /* may be a problem */ + } else { + /* + * At this point it's supposed to be + * something we can convert to a number. + */ + int n = 0; + + /* + * We reject it if it contains nonnumbers or + * it's not octal. + */ + while (tempmode[n] && !isspace(tempmode[n])) { + if (!isdigit(tempmode[n])) { + setErrstr( + pkg_gt(ERR_MODEALPHA)); + return (2); + } + + if (strchr("89abcdefABCDEF", + tempmode[n])) { + setErrstr( + pkg_gt(ERR_BASEINVAL)); + return (2); + } + n++; + } + + tempmode_t = strtol(tempmode, NULL, 8); + + /* + * We reject it if it contains inappropriate + * bits. + */ + if (tempmode_t & ~(S_IAMB | + S_ISUID | S_ISGID | S_ISVTX)) { + if (mapmode != MAPBUILD) { + tempmode_t = bad; + } else { + setErrstr(pkg_gt(ERR_MODEBITS)); + return (2); + } + } + *d = tempmode_t; + } + } + return (0); + } +} + +static int +getnum(FILE *fp, int base, long *d, long bad) +{ + int c, b; + + /* leading white space ignored */ + c = eatwhite(fp); + if (c == '?') { + *d = bad; + return (0); + } + + if ((c == EOF) || (c == '\n') || !isdigit(c)) { + (void) ungetc(c, fp); + return (1); + } + + *d = 0; + while (isdigit(c)) { + b = (c & 017); + if (b >= base) + return (2); + *d = (*d * base) + b; + c = getc(fp); + } + (void) ungetc(c, fp); + return (0); +} + +static int +getlnum(FILE *fp, int base, fsblkcnt_t *d, long bad) +{ + int c, b; + + /* leading white space ignored */ + c = eatwhite(fp); + if (c == '?') { + *d = bad; + return (0); + } + + if ((c == EOF) || (c == '\n') || !isdigit(c)) { + (void) ungetc(c, fp); + return (1); + } + + *d = 0; + while (isdigit(c)) { + b = (c & 017); + if (b >= base) + return (2); + *d = (*d * base) + b; + c = getc(fp); + } + (void) ungetc(c, fp); + return (0); +} + +/* + * Get a string from the file. Returns + * 0 if all OK + * 1 if nothing there + * -1 if string is too long + */ +static int +getstr(FILE *fp, char *sep, int n, char *str) +{ + int c; + + /* leading white space ignored */ + c = eatwhite(fp); + if ((c == EOF) || (c == '\n')) { + (void) ungetc(c, fp); + return (1); /* nothing there */ + } + + /* fill up string until space, tab, or separator */ + while (!strchr(" \t", c) && (!sep || !strchr(sep, c))) { + if (n-- < 1) { + *str = '\0'; + return (-1); /* too long */ + } + *str++ = (char)c; + c = getc(fp); + if ((c == EOF) || (c == '\n')) + break; /* no more on this line */ + } + *str = '\0'; + (void) ungetc(c, fp); + + return (0); +} + +static int +getend(FILE *fp) +{ + int c; + int n; + + n = 0; + do { + if ((c = getc(fp)) == EOF) + return (n); + if (!isspace(c)) + n++; + } while (c != '\n'); + return (n); +} + +static int +eatwhite(FILE *fp) +{ + int c; + + /* this test works around a side effect of getc() */ + if (feof(fp)) + return (EOF); + do + c = getc(fp); + while ((c == ' ') || (c == '\t')); + return (c); +} + +int +gpkgmapvfp(struct cfent *ept, VFP_T *vfp) +{ + int c; + boolean_t first_char = B_TRUE; + (void) strlcpy(ept->pkg_class, BADCLASS, sizeof (ept->pkg_class)); + (void) strlcpy(ept->ainfo.owner, d_owner, sizeof (ept->ainfo.owner)); + (void) strlcpy(ept->ainfo.group, d_group, sizeof (ept->ainfo.group)); + + setErrstr(NULL); + ept->volno = 0; + ept->ftype = BADFTYPE; + ept->pkg_class_idx = -1; + ept->path = NULL; + ept->ainfo.local = NULL; + ept->ainfo.mode = d_mode; + ept->ainfo.major = BADMAJOR; + ept->ainfo.minor = BADMINOR; + ept->cinfo.cksum = (-1L); + ept->cinfo.modtime = (-1L); + ept->cinfo.size = (-1L); + + ept->npkgs = 0; + + /* return error if no vfp specified */ + + if (vfp == (VFP_T *)NULL) { + return (-1); + } + +readline: + while (((c = vfpGetcNoInc(vfp)) != '\0') && (isspace(vfpGetc(vfp)))) + ; + + /* + * If the first character is not a digit, we assume that the + * volume number is 1. + */ + if (first_char && !isdigit(c)) { + ept->volno = 1; + } + first_char = B_FALSE; + + /* + * In case of hsfs the zero-padding of partial pages + * returned by mmap is not done properly. A separate bug has been filed + * on this. + */ + + if (vfp->_vfpCurr && (vfp->_vfpCurr > vfp->_vfpEnd)) { + return (0); + } + + switch (c) { + case '\0': + return (0); + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (ept->volno) { + setErrstr(pkg_gt(ERR_BAD_VOLUME_NUMBER)); + goto error; + } + do { + ept->volno = (ept->volno*10)+c-'0'; + c = vfpGetc(vfp); + } while (isdigit(c)); + if (ept->volno == 0) { + ept->volno = 1; + } + + goto readline; + + case ':': + case '#': + (void) findendvfp(&vfpGetCurrCharPtr(vfp)); + /*FALLTHRU*/ + case '\n': + /* + * Since we are going to scan the next line, + * we need to reset volume number and first_char. + */ + ept->volno = 0; + first_char = B_TRUE; + goto readline; + + case 'i': + ept->ftype = (char)c; + while (((c = vfpGetcNoInc(vfp)) != '\0') && + (isspace(vfpGetc(vfp)))) + ; + /*FALLTHRU*/ + case '.': + case '/': + vfpDecCurrPtr(vfp); + + if (getstrvfp(&vfpGetCurrCharPtr(vfp), "=", PATH_MAX, mypath)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_PATHNAME_FIELD)); + goto error; + } + ept->path = mypath; + c = vfpGetc(vfp); + if (c == '=') { + if (getstrvfp(&vfpGetCurrCharPtr(vfp), NULL, PATH_MAX, + mylocal)) { + setErrstr(pkg_gt(ERR_CANT_READ_LCLPATH)); + goto error; + } + ept->ainfo.local = mylocal; + } else { + vfpDecCurrPtr(vfp); + } + + if (ept->ftype == 'i') { + /* content info might exist */ + if (!getlnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (fsblkcnt_t *)&ept->cinfo.size, BADCONT) && + (getnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (long *)&ept->cinfo.cksum, BADCONT) || + getnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (long *)&ept->cinfo.modtime, BADCONT))) { + setErrstr(pkg_gt(ERR_CANNOT_READ_CONTENT_INFO)); + goto error; + } + } + + if (getendvfp(&vfpGetCurrCharPtr(vfp))) { + setErrstr(pkg_gt(ERR_EXTRA_TOKENS_PRESENT)); + return (-1); + } + return (1); + + case '?': + case 'f': + case 'v': + case 'e': + case 'l': + case 's': + case 'p': + case 'c': + case 'b': + case 'd': + case 'x': + ept->ftype = (char)c; + if (getstrvfp(&vfpGetCurrCharPtr(vfp), NULL, + CLSSIZ, ept->pkg_class)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_CLASS_TOKEN)); + goto error; + } + if (getstrvfp(&vfpGetCurrCharPtr(vfp), "=", PATH_MAX, mypath)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_PATHNAME_FIELD)); + goto error; + } + ept->path = mypath; + + c = vfpGetc(vfp); + if (c == '=') { + /* local path */ + if (getstrvfp(&vfpGetCurrCharPtr(vfp), NULL, + PATH_MAX, mylocal)) { + if (ept->ftype == 's' || ept->ftype == 'l') { + setErrstr(pkg_gt(ERR_READLINK)); + } else { + setErrstr( + pkg_gt(ERR_CANT_READ_LCLPATH)); + } + goto error; + } + ept->ainfo.local = mylocal; + } else if ((ept->ftype == 's') || (ept->ftype == 'l')) { + if ((c != '\0') && (c != '\n')) + (void) findendvfp(&vfpGetCurrCharPtr(vfp)); + setErrstr(pkg_gt(ERR_BAD_LINK_SPEC)); + return (-1); + } else { + vfpDecCurrPtr(vfp); + } + break; + + default: + setErrstr(pkg_gt(ERR_UNKNOWN_FTYPE)); +error: + (void) findendvfp(&vfpGetCurrCharPtr(vfp)); + return (-1); + } + + if (((ept->ftype == 's') || (ept->ftype == 'l')) && + (ept->ainfo.local == NULL)) { + setErrstr(pkg_gt(ERR_NO_LINKSOURCE)); + goto error; + } + + if (((ept->ftype == 'c') || (ept->ftype == 'b'))) { + ept->ainfo.major = BADMAJOR; + ept->ainfo.minor = BADMINOR; + + if (getnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (long *)&ept->ainfo.major, BADMAJOR) || + getnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (long *)&ept->ainfo.minor, BADMINOR)) { + setErrstr(pkg_gt(ERR_CANNOT_READ_MM_DEVNUMS)); + goto error; + } + } + + /* + * Links and information files don't have attributes associated with + * them. The following either resolves potential variables or passes + * them through. Mode is tested for validity to some degree. BAD??? + * is returned to indicate that no meaningful mode was provided. A + * higher authority will decide if that's OK or not. CUR??? means that + * the prototype file specifically requires a wildcard ('?') for + * that entry. We issue an error if attributes were entered wrong. + * We just return BAD??? if there was no entry at all. + */ + if ((ept->ftype == 'd') || (ept->ftype == 'x') || (ept->ftype == 'c') || + (ept->ftype == 'b') || (ept->ftype == 'p') || + (ept->ftype == 'f') || (ept->ftype == 'v') || + (ept->ftype == 'e')) { + int retval; + + retval = getvalmodevfp(&vfpGetCurrCharPtr(vfp), + &(ept->ainfo.mode), + CURMODE, (mapmode != MAPNONE)); + + if (retval == 1) { + goto end; /* nothing else on the line */ + } else if (retval == 2) { + goto error; /* mode is too no good */ + } + + /* owner & group should be here */ + if ((retval = getstrvfp(&vfpGetCurrCharPtr(vfp), NULL, ATRSIZ, + ept->ainfo.owner)) == 1) + goto end; /* no owner or group - warning */ + if (retval == -1) { + setErrstr(pkg_gt(ERR_OWNTOOLONG)); + goto error; + } + + if ((retval = getstrvfp(&vfpGetCurrCharPtr(vfp), NULL, ATRSIZ, + ept->ainfo.group)) == 1) + goto end; /* no group - warning */ + if (retval == -1) { + setErrstr(pkg_gt(ERR_GRPTOOLONG)); + goto error; + } + + /* Resolve the parameters if required. */ + if (mapmode != MAPNONE) { + if (mapvar(mapmode, ept->ainfo.owner)) { + (void) snprintf(getErrbufAddr(), + getErrbufSize(), pkg_gt(ERR_NOVAR), + maptype, ept->ainfo.owner); + setErrstr(getErrbufAddr()); + goto error; + } + if (mapvar(mapmode, ept->ainfo.group)) { + (void) snprintf(getErrbufAddr(), + getErrbufSize(), pkg_gt(ERR_NOVAR), + maptype, ept->ainfo.group); + setErrstr(getErrbufAddr()); + goto error; + } + } + } + + if ((ept->ftype == 'i') || (ept->ftype == 'f') || + (ept->ftype == 'v') || (ept->ftype == 'e')) { + /* look for content description */ + if (!getlnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (fsblkcnt_t *)&ept->cinfo.size, BADCONT) && + (getnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (long *)&ept->cinfo.cksum, BADCONT) || + getnumvfp(&vfpGetCurrCharPtr(vfp), 10, + (long *)&ept->cinfo.modtime, BADCONT))) { + setErrstr(pkg_gt(ERR_CANNOT_READ_CONTENT_INFO)); + goto error; + } + } + + if (ept->ftype == 'i') + goto end; + +end: + if (getendvfp(&vfpGetCurrCharPtr(vfp)) && ept->pinfo) { + setErrstr(pkg_gt(ERR_EXTRA_TOKENS_PRESENT)); + return (-1); + } + +done: + return (1); +} + +/* + * Get and validate the mode attribute. This returns an error if + * 1. the mode string is too long + * 2. the mode string includes alpha characters + * 3. the mode string is not octal + * 4. mode string is an install parameter + * 5. mode is an unresolved build parameter and MAPBUILD is + * in effect. + * If the mode is a build parameter, it is + * 1. returned as is if MAPNONE is in effect + * 2. evaluated if MAPBUILD is in effect + * + * NOTE : We use "mapmode!=MAPBUILD" to gather that it is install + * time. At install time we just fix a mode with bad bits set by + * setting it to CURMODE. This should be an error in a few releases + * (2.8 maybe) but faulty modes are so common in existing packages + * that this is a reasonable exception. -- JST 1994-11-9 + * + * RETURNS + * 0 if mode is being returned as a valid value + * 1 if no attributes are present on the line + * 2 if there was a fundamental error + */ +static int +getvalmodevfp(char **cp, mode_t *d, long bad, int map) +{ + char tempmode[ATRSIZ+1]; + mode_t tempmode_t; + int retval; + int n; + + if ((retval = getstrvfp(cp, NULL, sizeof (tempmode), tempmode)) == 1) { + return (1); + } else if (retval == -1) { + setErrstr(pkg_gt(ERR_MODELONG)); + return (2); + } + + /* + * If it isn't a '?' (meaning go with whatever mode is + * there), validate the mode and convert it to a mode_t. The + * "bad" variable here is a misnomer. It doesn't necessarily + * mean bad. + */ + if (tempmode[0] == '?') { + *d = WILDCARD; + return (0); + } + + /* + * Mode may not be an install parameter or a + * non-build parameter. + */ + + if (tempmode[0] == '$' && + (isupper(tempmode[1]) || !islower(tempmode[1]))) { + setErrstr(pkg_gt(ERR_IMODE)); + return (2); + } + + if ((map) && (mapvar(mapmode, tempmode))) { + (void) snprintf(getErrbufAddr(), getErrbufSize(), + pkg_gt(ERR_NOVAR), maptype, tempmode); + setErrstr(getErrbufAddr()); + return (2); + } + + if (tempmode[0] == '$') { + *d = BADMODE; /* may be a problem */ + return (0); + } + + /* it's supposed to be something we can convert to a number */ + + n = 0; + + /* reject it if it contains nonnumbers or it's not octal */ + + while (tempmode[n] && !isspace(tempmode[n])) { + if (!isdigit(tempmode[n])) { + setErrstr(pkg_gt(ERR_MODEALPHA)); + return (2); + } + + if (strchr("89abcdefABCDEF", tempmode[n])) { + setErrstr(pkg_gt(ERR_BASEINVAL)); + return (2); + } + n++; + } + + tempmode_t = strtol(tempmode, NULL, 8); + + /* + * We reject it if it contains inappropriate + * bits. + */ + if (tempmode_t & (~(S_IAMB | S_ISUID | S_ISGID | S_ISVTX))) { + if (mapmode == MAPBUILD) { + setErrstr(pkg_gt(ERR_MODEBITS)); + return (2); + } + tempmode_t = bad; + } + + *d = tempmode_t; + + return (0); +} + +int +getnumvfp(char **cp, int base, long *d, long bad) +{ + int c; + char *p = *cp; + + if (*p == '\0') { + return (0); + } + + /* leading white space ignored */ + while (((c = *p) != '\0') && (isspace(*p++))) + ; + if (c == '?') { + *d = bad; + *cp = p; + return (0); + } + + if ((c == '\0') || (c == '\n') || !isdigit(c)) { + p--; + *cp = p; + return (1); + } + + *d = 0; + while (isdigit(c)) { + *d = (*d * base) + (c & 017); + c = *p++; + } + p--; + *cp = p; + return (0); +} + +int +getlnumvfp(char **cp, int base, fsblkcnt_t *d, long bad) +{ + int c; + char *p = *cp; + + if (*p == '\0') { + return (0); + } + + /* leading white space ignored */ + while (((c = *p) != '\0') && (isspace(*p++))) + ; + if (c == '?') { + *d = bad; + *cp = p; + return (0); + } + + if ((c == '\0') || (c == '\n') || !isdigit(c)) { + p--; + *cp = p; + return (1); + } + + *d = 0; + while (isdigit(c)) { + *d = (*d * base) + (c & 017); + c = *p++; + } + p--; + *cp = p; + return (0); +} + +static int +getstrvfp(char **cp, char *sep, int n, char *str) +{ + char delims[256]; + int c; + char *p = *cp; + char *p1; + size_t len; + + if (*p == '\0') { + return (1); + } + + /* leading white space ignored */ + + while (((c = *p) != '\0') && (isspace(*p++))) + ; + if ((c == '\0') || (c == '\n')) { + p--; + *cp = p; + return (1); /* nothing there */ + } + + p--; + + /* generate complete list of delimiters to scan for */ + + (void) strlcpy(delims, " \t\n", sizeof (delims)); + if ((sep != (char *)NULL) && (*sep != '\0')) { + (void) strlcat(delims, sep, sizeof (delims)); + } + + /* compute length based on delimiter found or not */ + + p1 = strpbrk(p, delims); + if (p1 == (char *)NULL) { + len = strlen(p); + } else { + len = (ptrdiff_t)p1 - (ptrdiff_t)p; + } + + /* if string will fit in result buffer copy string and return success */ + + if (len < n) { + (void) memcpy(str, p, len); + str[len] = '\0'; + p += len; + *cp = p; + return (0); + } + + /* result buffer too small; copy partial string, return error */ + (void) memcpy(str, p, n-1); + str[n-1] = '\0'; + p += n; + *cp = p; + return (-1); +} + +/* + * Name: getendvfp + * Description: Locate the end of the current line given a pointer into a buffer + * containing characters that is null terminated. + * Arguments: char **cp - pointer to pointer to null-terminated string buffer + * Returns: int == 0 -- no non-space characters preceeded the newline + * != 0 -- one or more non-space characters preceeded newline + * Effects: cp is updated to point to the first character PAST the first new + * line character found. If no newline character is found, cp is + * updated to point to the '\0' at the end of the buffer. + */ + +static int +getendvfp(char **cp) +{ + int n; + char *p = *cp; + + n = 0; + + /* if at end of buffer return no more characters left */ + + if (*p == '\0') { + return (0); + } + + /* find the first null or end of line character */ + + while ((*p != '\0') && (*p != '\n')) { + if (n == 0) { + if (!isspace(*p)) { + n++; + } + } + p++; + } + + /* if at newline, increment pointer to first character past newline */ + + if (*p == '\n') { + p++; + } + + /* set return pointer to null or first character past newline */ + + *cp = p; + + /* return space/nospace indicator */ + + return (n); +} + +/* + * Name: findendvfp + * Description: Locate the end of the current line given a pointer into a buffer + * containing characters that is null terminated. + * Arguments: char **cp - pointer to pointer to null-terminated string buffer + * Returns: none + * Effects: cp is updated to point to the first character PAST the first new + * line character found. If no newline character is found, cp is + * updated to point to the '\0' at the end of the buffer. + */ + +static void +findendvfp(char **cp) +{ + char *p1; + char *p = *cp; + + /* if at end of buffer return no more characters left */ + + if (*p == '\0') { + return; + } + + /* find the end of the line */ + + p1 = strchr(p, '\n'); + if (p1 != (char *)NULL) { + *cp = ++p1; + return; + } + + /* no newline found - point to null terminator */ + + *cp = strchr(p, '\0'); +} diff --git a/usr/src/lib/libpkg/common/handlelocalfs.c b/usr/src/lib/libpkg/common/handlelocalfs.c new file mode 100644 index 0000000000..9130cb1417 --- /dev/null +++ b/usr/src/lib/libpkg/common/handlelocalfs.c @@ -0,0 +1,150 @@ +/* + * + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * +*/ + + +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> +#include <libscf.h> + +#define MAX_TRY 15 +static boolean_t fs_temporarily_enabled = B_FALSE; +char svm_core_svcs[] = "system/filesystem/local:default"; + +/* + * Name: enable_local_fs + * Description: If the SMF service system/filesystem/local:default is not + * enabled, then this function enables the service, so that, + * all the local filesystems are mounted. + * Arguments: None + * Returns: B_TRUE on success; B_FALSE on error. + */ +boolean_t +enable_local_fs(void) +{ + char *cur_smf_state; + int i; + boolean_t fs_enabled_here = B_FALSE; + + if (fs_temporarily_enabled) { + return (B_TRUE); + } + + if ((cur_smf_state = smf_get_state(svm_core_svcs)) != NULL) { + if (strcmp(cur_smf_state, SCF_STATE_STRING_DISABLED) == 0) { + if (smf_enable_instance(svm_core_svcs, SMF_TEMPORARY) + != 0) { + free(cur_smf_state); + return (B_FALSE); + } + + fs_enabled_here = B_TRUE; + + } else if (strcmp(cur_smf_state, SCF_STATE_STRING_ONLINE) + == 0) { + free(cur_smf_state); + return (B_TRUE); + } else if (strcmp(cur_smf_state, SCF_STATE_STRING_OFFLINE) + != 0) { + free(cur_smf_state); + return (B_FALSE); + } + + free(cur_smf_state); + + } else { + return (B_FALSE); + } + + for (i = 0; i < MAX_TRY; i++) { + if ((cur_smf_state = smf_get_state(svm_core_svcs)) != NULL) { + if (strcmp(cur_smf_state, SCF_STATE_STRING_ONLINE) + == 0) { + free(cur_smf_state); + if (fs_enabled_here) { + fs_temporarily_enabled = B_TRUE; + } + return (B_TRUE); + } else if ((strcmp(cur_smf_state, + SCF_STATE_STRING_OFFLINE) == 0) || + (strcmp(cur_smf_state, SCF_STATE_STRING_DISABLED) == 0)) { + (void) sleep(1); + free(cur_smf_state); + } else { + free(cur_smf_state); + return (B_FALSE); + } + } else { + return (B_FALSE); + } + } + + return (B_FALSE); +} + +/* + * Name: restore_local_fs + * Description: If the SMF service system/filesystem/local:default was + * enabled using enable_local_fs(), then this function disables + * the service. + * Arguments: None + * Returns: B_TRUE on success; B_FALSE on error. + */ +boolean_t +restore_local_fs(void) +{ + int i; + char *cur_smf_state; + + if (!fs_temporarily_enabled) { + return (B_TRUE); + } + + if (smf_disable_instance(svm_core_svcs, SMF_TEMPORARY) != 0) { + return (B_FALSE); + } + + for (i = 0; i < MAX_TRY; i++) { + if ((cur_smf_state = smf_get_state(svm_core_svcs)) != NULL) { + if (strcmp(cur_smf_state, SCF_STATE_STRING_DISABLED) + == 0) { + fs_temporarily_enabled = B_FALSE; + free(cur_smf_state); + break; + } + (void) sleep(1); + + free(cur_smf_state); + } else { + return (B_FALSE); + } + } + + return (!fs_temporarily_enabled); +} diff --git a/usr/src/lib/libpkg/common/isdir.c b/usr/src/lib/libpkg/common/isdir.c new file mode 100644 index 0000000000..55b59d5e67 --- /dev/null +++ b/usr/src/lib/libpkg/common/isdir.c @@ -0,0 +1,391 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <archives.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "pkglocale.h" +#include "pkglibmsgs.h" + +/* + * Defines for cpio/compression checks. + */ +#define BIT_MASK 0x1f +#define BLOCK_MASK 0x80 + +#define MASK_CK(x, y) (((x) & (y)) == (y)) +#define ISCOMPCPIO ((unsigned char) cm.c_mag[0] == m_h[0] && \ + (unsigned char) cm.c_mag[1] == m_h[1] && \ + (MASK_CK((unsigned char) cm.c_mag[2], BLOCK_MASK) || \ + MASK_CK((unsigned char) cm.c_mag[2], BIT_MASK))) + +#define ISCPIO (cm.b_mag != CMN_BIN && \ + (strcmp(cm.c_mag, CMS_ASC) == 0) && \ + (strcmp(cm.c_mag, CMS_CHR) == 0) && \ + (strcmp(cm.c_mag, CMS_CRC) == 0)) + +/* location of distributed file system types database */ + +#define REMOTE_FS_DBFILE "/etc/dfs/fstypes" + +/* character array used to hold dfs types database contents */ + +static long numRemoteFstypes = -1; +static char **remoteFstypes = (char **)NULL; + +/* forward declarations */ + +static void _InitRemoteFstypes(void); + +int isFdRemote(int a_fd); +int isPathRemote(char *a_path); +int isFstypeRemote(char *a_fstype); +int isdir(char *path); +int isfile(char *dir, char *file); +int iscpio(char *path, int *iscomp); + +/* + * Name: isdir + * Description: determine if specified path exists and is a directory + * Arguments: path - pointer to string representing the path to verify + * returns: 0 - directory exists + * 1 - directory does not exist or is not a directory + * NOTE: errno is set appropriately + */ + +int +isdir(char *path) +{ + struct stat statbuf; + + /* return error if path does not exist */ + + if (stat(path, &statbuf) != 0) { + return (1); + } + + /* return error if path is not a directory */ + + if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { + errno = ENOTDIR; + return (1); + } + + return (0); +} + +/* + * Name: isfile + * Description: determine if specified path exists and is a directory + * Arguments: dir - pointer to string representing the directory where + * the file is located + * == NULL - use "file" argument only + * file - pointer to string representing the file to verify + * Returns: 0 - success - file exists + * 1 - failure - file does not exist OR is not a file + * NOTE: errno is set appropriately + */ + +int +isfile(char *dir, char *file) +{ + struct stat statbuf; + char path[PATH_MAX]; + + /* construct full path if directory specified */ + + if (dir) { + (void) snprintf(path, sizeof (path), "%s/%s", dir, file); + file = path; + } + + /* return error if path does not exist */ + + if (stat(file, &statbuf) != 0) { + return (1); + } + + /* return error if path is a directory */ + + if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { + errno = EISDIR; + return (1); + } + + /* return error if path is not a file */ + + if ((statbuf.st_mode & S_IFMT) != S_IFREG) { + errno = EINVAL; + return (1); + } + + return (0); +} + +int +iscpio(char *path, int *iscomp) +{ + /* + * Compressed File Header. + */ + unsigned char m_h[] = { "\037\235" }; /* 1F 9D */ + + static union { + short int b_mag; + char c_mag[CMS_LEN]; + } cm; + + struct stat statb; + int fd; + + + *iscomp = 0; + + if ((fd = open(path, O_RDONLY, 0)) == -1) { + if (errno != ENOENT) { + perror(""); + (void) fprintf(stderr, pkg_gt(ERR_ISCPIO_OPEN), path); + } + return (0); + } else { + if (fstat(fd, &statb) == -1) { + perror(""); + (void) fprintf(stderr, pkg_gt(ERR_ISCPIO_FSTAT), path); + (void) close(fd); + return (0); + } else { + if (S_ISREG(statb.st_mode)) { /* Must be a file */ + if (read(fd, cm.c_mag, sizeof (cm.c_mag)) != + sizeof (cm.c_mag)) { + perror(""); + (void) fprintf(stderr, + pkg_gt(ERR_ISCPIO_READ), path); + (void) close(fd); + return (0); + } + /* + * Try to determine if the file is a compressed + * file, if that fails, try to determine if it + * is a cpio archive, if that fails, then we + * fail! + */ + if (ISCOMPCPIO) { + *iscomp = 1; + (void) close(fd); + return (1); + } else if (ISCPIO) { + (void) fprintf(stderr, + pkg_gt(ERR_ISCPIO_NOCPIO), + path); + (void) close(fd); + return (0); + } + (void) close(fd); + return (1); + } else { + (void) close(fd); + return (0); + } + } + } +} + +/* + * Name: isPathRemote + * Description: determine if a path object is local or remote + * Arguments: a_path - [RO, *RO] - (char *) + * Pointer to string representing the path to check + * Returns: int + * 1 - the path is remote + * 0 - the path is local to this system + * -1 - cannot determine if path is remote or local + */ + +int +isPathRemote(char *a_path) +{ + int r; + struct stat statbuf; + + r = lstat(a_path, &statbuf); + if (r < 0) { + return (-1); + } + + return (isFstypeRemote(statbuf.st_fstype)); +} + +/* + * Name: isFdRemote + * Description: determine if an open file is local or remote + * Arguments: a_fd - [RO, *RO] - (int) + * Integer representing open file to check + * Returns: int + * 1 - the path is remote + * 0 - the path is local to this system + * -1 - cannot determine if path is remote or local + */ + +int +isFdRemote(int a_fd) +{ + int r; + struct stat statbuf; + + r = fstat(a_fd, &statbuf); + if (r < 0) { + return (-1); + } + + return (isFstypeRemote(statbuf.st_fstype)); +} + +/* + * Name: isFstypeRemote + * Description: determine if a file system type is remote (distributed) + * Arguments: a_fstype - [RO, *RO] - (char *) + * Pointer to string representing the file system type + * to check + * Returns: int + * 1 - the file system type is remote + * 0 - the file system type is local to this system + */ + +int +isFstypeRemote(char *a_fstype) +{ + int i; + + /* initialize the list if it is not yet initialized */ + + _InitRemoteFstypes(); + + /* scan the list looking for the specified type */ + + for (i = 0; i < numRemoteFstypes; i++) { + if (strcmp(remoteFstypes[i], a_fstype) == 0) { + return (1); + } + } + + /* type not found in remote file system type list - is not remote */ + + return (0); +} + +/* + * Name: _InitRemoteFstypes + * Description: initialize table of remote file system type names + * Arguments: none + * Returns: none + * Side Effects: + * - The global array "(char **)remoteFstypes" is set to the + * address of an array of string pointers, each of which represents + * a single remote file system type + * - The global variable "(long) numRemoteFstypes" is set to the total + * number of remote file system type strings (names) that are + * contained in the "remoteFstypes" global array. + * - numRemoteFstypes is initialized to "-1" before any attempt has been + * made to read the remote file system type name database. + */ +static void +_InitRemoteFstypes(void) +{ + FILE *fp; + char line_buf[LINE_MAX]; + + /* return if already initialized */ + + if (numRemoteFstypes > 0) { + return; + } + + /* if list is uninitialized, start with zero */ + + if (numRemoteFstypes == -1) { + numRemoteFstypes = 0; + } + + /* open the remote file system type database file */ + + if ((fp = fopen(REMOTE_FS_DBFILE, "r")) == NULL) { + /* no remote type database: use predefined remote types */ + remoteFstypes = (char **)realloc(remoteFstypes, + sizeof (char *) * (numRemoteFstypes+3)); + remoteFstypes[numRemoteFstypes++] = "nfs"; /* +1 */ + remoteFstypes[numRemoteFstypes++] = "autofs"; /* +2 */ + remoteFstypes[numRemoteFstypes++] = "cachefs"; /* +3 */ + return; + } + + /* + * Read the remote file system type database; from fstypes(4): + * + * fstypes resides in directory /etc/dfs and lists distributed file + * system utilities packages installed on the system. For each installed + * distributed file system type, there is a line that begins with the + * file system type name (for example, ``nfs''), followed by white space + * and descriptive text. + * + * Lines will look at lot like this: + * + * nfs NFS Utilities + * autofs AUTOFS Utilities + * cachefs CACHEFS Utilities + */ + + while (fgets(line_buf, sizeof (line_buf), fp) != NULL) { + char buf[LINE_MAX]; + static char format[128] = {'\0'}; + + if (format[0] == '\0') { + /* create bounded format: %ns */ + (void) snprintf(format, sizeof (format), + "%%%ds", sizeof (buf)-1); + } + + (void) sscanf(line_buf, format, buf); + + remoteFstypes = realloc(remoteFstypes, + sizeof (char *) * (numRemoteFstypes+1)); + remoteFstypes[numRemoteFstypes++] = strdup(buf); + } + + /* close database file and return */ + + (void) fclose(fp); +} diff --git a/usr/src/lib/libpkg/common/keystore.c b/usr/src/lib/libpkg/common/keystore.c new file mode 100644 index 0000000000..ad94b96c2d --- /dev/null +++ b/usr/src/lib/libpkg/common/keystore.c @@ -0,0 +1,2474 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Module: keystore.c + * Description: This module contains the structure definitions for processing + * package keystore files. + */ + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <strings.h> +#include <libintl.h> +#include <time.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/pkcs12.h> +#include <openssl/asn1.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/safestack.h> +#include <openssl/stack.h> +#include "p12lib.h" +#include "pkgerr.h" +#include "keystore.h" +#include "pkglib.h" +#include "pkglibmsgs.h" + +typedef struct keystore_t { + boolean_t dirty; + boolean_t new; + char *path; + char *passphrase; + /* truststore handles */ + int cafd; + STACK_OF(X509) *cacerts; + char *capath; + + /* user certificate handles */ + STACK_OF(X509) *clcerts; + char *clpath; + + /* private key handles */ + STACK_OF(EVP_PKEY) *pkeys; + char *keypath; +} keystore_t; + +/* local routines */ +static keystore_t *new_keystore(void); +static void free_keystore(keystore_t *); +static boolean_t verify_keystore_integrity(PKG_ERR *, keystore_t *); +static boolean_t check_password(PKCS12 *, char *); +static boolean_t resolve_paths(PKG_ERR *, char *, char *, + long, keystore_t *); +static boolean_t lock_keystore(PKG_ERR *, long, keystore_t *); + +static boolean_t unlock_keystore(PKG_ERR *, keystore_t *); +static boolean_t read_keystore(PKG_ERR *, keystore_t *, + keystore_passphrase_cb); +static boolean_t write_keystore(PKG_ERR *, keystore_t *, + keystore_passphrase_cb); +static boolean_t write_keystore_file(PKG_ERR *, char *, PKCS12 *); +static boolean_t clear_keystore_file(PKG_ERR *, char *); +static PKCS12 *read_keystore_file(PKG_ERR *, char *); +static char *get_time_string(ASN1_TIME *); + +/* locking routines */ +static boolean_t restore_keystore_file(PKG_ERR *, char *); +static int file_lock(int, int, int); +static int file_unlock(int); +static boolean_t file_lock_test(int, int); +static boolean_t file_empty(char *); +static boolean_t get_keystore_passwd(PKG_ERR *err, PKCS12 *p12, + keystore_passphrase_cb cb, keystore_t *keystore); +static boolean_t wait_restore(int, char *, char *, char *); + +#define KEYSTORE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) + +/* wait on other keystore access for 1 minute before giving up */ +#define LOCK_TIMEOUT 60 + +/* + * print_certs - prints certificates out of a keystore, to a file. + * + * Arguments: + * err - Error object to append errors to + * keystore - Keystore on which to operate + * alias - Name of certificate to print, NULL means print all + * format - Format in which to print certificates + * outfile - Where to print certificates + * + * Returns: + * 0 - Success + * non-zero - Failure, errors added to err + */ +int +print_certs(PKG_ERR *err, keystore_handle_t keystore_h, char *alias, + keystore_encoding_format_t format, FILE *outfile) +{ + int i; + X509 *cert; + char *fname = NULL; + boolean_t found = B_FALSE; + keystore_t *keystore = keystore_h; + + if (keystore->clcerts != NULL) { + /* print out each client cert */ + for (i = 0; i < sk_X509_num(keystore->clcerts); i++) { + cert = sk_X509_value(keystore->clcerts, i); + (void) sunw_get_cert_fname(GETDO_COPY, cert, + &fname); + + if (fname == NULL) { + /* no name recorded, keystore is corrupt */ + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_NO_ALIAS), + get_subject_display_name(cert)); + return (1); + } + + if ((alias != NULL) && (!streq(alias, fname))) { + /* name does not match, skip it */ + (void) OPENSSL_free(fname); + fname = NULL; + continue; + } else { + found = B_TRUE; + (void) print_cert(err, cert, format, + fname, B_FALSE, outfile); + (void) OPENSSL_free(fname); + fname = NULL; + } + } + } + + if (fname != NULL) { + (void) OPENSSL_free(fname); + fname = NULL; + } + + if (keystore->cacerts != NULL) { + /* print out each trusted cert */ + for (i = 0; i < sk_X509_num(keystore->cacerts); i++) { + cert = sk_X509_value(keystore->cacerts, i); + (void) sunw_get_cert_fname(GETDO_COPY, + cert, &fname); + + if (fname == NULL) { + /* no name recorded, keystore is corrupt */ + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_NO_ALIAS), + get_subject_display_name(cert)); + return (1); + } + + if ((alias != NULL) && (!streq(alias, fname))) { + /* name does not match, skip it */ + (void) OPENSSL_free(fname); + fname = NULL; + continue; + } else { + found = B_TRUE; + (void) print_cert(err, cert, format, + fname, B_TRUE, outfile); + (void) OPENSSL_free(fname); + fname = NULL; + } + } + } + + if (fname != NULL) { + (void) OPENSSL_free(fname); + fname = NULL; + } + + if (found) { + return (0); + } else { + /* no certs printed */ + if (alias != NULL) { + pkgerr_add(err, PKGERR_NOALIASMATCH, + gettext(ERR_KEYSTORE_NOCERT), + alias, keystore->path); + } else { + pkgerr_add(err, PKGERR_NOPUBKEY, + gettext(ERR_KEYSTORE_NOPUBCERTS), + keystore->path); + pkgerr_add(err, PKGERR_NOCACERT, + gettext(ERR_KEYSTORE_NOCACERTS), + keystore->path); + } + return (1); + } +} + +/* + * print_cert - prints a single certificate, to a file + * + * Arguments: + * err - Error object to append errors to + * x - The certificate to print + * alias - Name of certificate to print + * format - Format in which to print certificate + * outfile - Where to print certificate + * + * Returns: + * 0 - Success + * non-zero - Failure, errors added to err + */ +int print_cert(PKG_ERR *err, X509 *x, + keystore_encoding_format_t format, char *alias, boolean_t is_trusted, + FILE *outfile) +{ + + char *vdb_str; + char *vda_str; + char vd_str[ATTR_MAX]; + int ret = 0; + char *cn_str, *icn_str, *typ_str; + char *tmp; + char *md5_fp; + char *sha1_fp; + int len; + + /* need to localize the word "Fingerprint", hence these pointers */ + char md5_label[ATTR_MAX]; + char sha1_label[ATTR_MAX]; + + if (is_trusted) { + typ_str = gettext(MSG_KEYSTORE_TRUSTED); + } else { + typ_str = gettext(MSG_KEYSTORE_UNTRUSTED); + } + + if ((cn_str = get_subject_display_name(x)) == NULL) { + cn_str = gettext(MSG_KEYSTORE_UNKNOWN); + } + + if ((icn_str = get_issuer_display_name(x)) == NULL) { + icn_str = gettext(MSG_KEYSTORE_UNKNOWN); + } + + vdb_str = xstrdup(get_time_string(X509_get_notBefore(x))); + vda_str = xstrdup(get_time_string(X509_get_notAfter(x))); + if (((len = snprintf(vd_str, ATTR_MAX, "<%s> - <%s>", + vdb_str, vda_str)) < 0) || (len >= ATTR_MAX)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), vdb_str); + ret = 1; + goto cleanup; + } + + if ((tmp = get_fingerprint(x, EVP_md5())) == NULL) { + md5_fp = gettext(MSG_KEYSTORE_UNKNOWN); + } else { + /* + * make a copy, otherwise the next call to get_fingerprint + * will overwrite this one + */ + md5_fp = xstrdup(tmp); + } + + if ((tmp = get_fingerprint(x, EVP_sha1())) == NULL) { + sha1_fp = gettext(MSG_KEYSTORE_UNKNOWN); + } else { + sha1_fp = xstrdup(tmp); + } + + (void) snprintf(md5_label, ATTR_MAX, "%s %s", + OBJ_nid2sn(EVP_MD_type(EVP_md5())), + /* i18n: 14 characters max */ + gettext(MSG_KEYSTORE_FP)); + + (void) snprintf(sha1_label, ATTR_MAX, "%s %s", + OBJ_nid2sn(EVP_MD_type(EVP_sha1())), + /* i18n: 14 characters max */ + gettext(MSG_KEYSTORE_FP)); + + switch (format) { + case KEYSTORE_FORMAT_PEM: + (void) PEM_write_X509(outfile, x); + break; + case KEYSTORE_FORMAT_DER: + (void) i2d_X509_fp(outfile, x); + break; + case KEYSTORE_FORMAT_TEXT: + (void) fprintf(outfile, "%18s: %s\n", + /* i18n: 18 characters max */ + gettext(MSG_KEYSTORE_AL), alias); + (void) fprintf(outfile, "%18s: %s\n", + /* i18n: 18 characters max */ + gettext(MSG_KEYSTORE_CN), cn_str); + (void) fprintf(outfile, "%18s: %s\n", + /* i18n: 18 characters max */ + gettext(MSG_KEYSTORE_TY), typ_str); + (void) fprintf(outfile, "%18s: %s\n", + /* i18n: 18 characters max */ + gettext(MSG_KEYSTORE_IN), icn_str); + (void) fprintf(outfile, "%18s: %s\n", + /* i18n: 18 characters max */ + gettext(MSG_KEYSTORE_VD), vd_str); + (void) fprintf(outfile, "%18s: %s\n", md5_label, md5_fp); + (void) fprintf(outfile, "%18s: %s\n", sha1_label, sha1_fp); + (void) fprintf(outfile, "\n"); + break; + default: + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), + __FILE__, __LINE__); + ret = 1; + goto cleanup; + } + +cleanup: + if (md5_fp != NULL) + free(md5_fp); + if (sha1_fp != NULL) + free(sha1_fp); + if (vda_str != NULL) + free(vda_str); + if (vdb_str != NULL) + free(vdb_str); + return (ret); +} + +/* + * open_keystore - Initialize new keystore object for + * impending access. + * + * Arguments: + * err - Error object to append errors to + * keystore_file - Base filename or directory of keystore + * app - Application making request + * passwd - Password used to decrypt keystore + * flags - Control flags used to control access mode and behavior + * result - Resulting keystore object stored here on success + * + * Returns: + * 0 - Success - result contains a pointer to the opened keystore + * non-zero - Failure, errors added to err + */ +int +open_keystore(PKG_ERR *err, char *keystore_file, char *app, + keystore_passphrase_cb cb, long flags, keystore_handle_t *result) +{ + int ret = 0; + keystore_t *tmpstore; + + tmpstore = new_keystore(); + + tmpstore->dirty = B_FALSE; + tmpstore->new = B_FALSE; + tmpstore->path = xstrdup(keystore_file); + + if (!resolve_paths(err, keystore_file, app, flags, tmpstore)) { + /* unable to determine keystore paths */ + pkgerr_add(err, PKGERR_CORRUPT, gettext(ERR_KEYSTORE_REPAIR), + keystore_file); + ret = 1; + goto cleanup; + } + + if (!verify_keystore_integrity(err, tmpstore)) { + /* unable to repair keystore */ + pkgerr_add(err, PKGERR_CORRUPT, gettext(ERR_KEYSTORE_REPAIR), + keystore_file); + ret = 1; + goto cleanup; + } + + if (!lock_keystore(err, flags, tmpstore)) { + pkgerr_add(err, PKGERR_LOCKED, gettext(ERR_KEYSTORE_LOCKED), + keystore_file); + ret = 1; + goto cleanup; + } + + /* now that we have locked the keystore, go ahead and read it */ + if (!read_keystore(err, tmpstore, cb)) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_PARSE), + keystore_file); + ret = 1; + goto cleanup; + } + + *result = tmpstore; + tmpstore = NULL; + +cleanup: + if (tmpstore != NULL) + free_keystore(tmpstore); + return (ret); +} + +/* + * new_keystore - Allocates and initializes a Keystore object + * + * Arguments: + * NONE + * + * Returns: + * NULL - out of memory + * otherwise, returns a pointer to the newly allocated object, + * which should be freed with free_keystore() when no longer + * needed. + */ +static keystore_t +*new_keystore(void) +{ + keystore_t *tmpstore; + + if ((tmpstore = (keystore_t *)malloc(sizeof (keystore_t))) == NULL) { + return (NULL); + } + tmpstore->dirty = B_FALSE; + tmpstore->new = B_FALSE; + tmpstore->path = NULL; + tmpstore->passphrase = NULL; + tmpstore->cafd = -1; + tmpstore->cacerts = NULL; + tmpstore->capath = NULL; + tmpstore->clcerts = NULL; + tmpstore->clpath = NULL; + tmpstore->pkeys = NULL; + tmpstore->keypath = NULL; + + return (tmpstore); +} + +/* + * free_keystore - Deallocates a Keystore object + * + * Arguments: + * keystore - The keystore to deallocate + * + * Returns: + * NONE + */ +static void +free_keystore(keystore_t *keystore) +{ + if (keystore->path != NULL) + free(keystore->path); + if (keystore->capath != NULL) + free(keystore->capath); + if (keystore->passphrase != NULL) + free(keystore->passphrase); + if (keystore->clpath != NULL) + free(keystore->clpath); + if (keystore->keypath != NULL) + free(keystore->keypath); + + if (keystore->pkeys != NULL) { + sk_EVP_PKEY_pop_free(keystore->pkeys, + sunw_evp_pkey_free); + } + if (keystore->clcerts != NULL) + sk_X509_free(keystore->clcerts); + if (keystore->cacerts != NULL) + sk_X509_free(keystore->cacerts); + free(keystore); +} + +/* + * close_keystore - Writes keystore to disk if needed, then + * unlocks and closes keystore. + * + * Arguments: + * err - Error object to append errors to + * keystore - Keystore which should be closed + * passwd - Password used to encrypt keystore + * + * Returns: + * 0 - Success - keystore is committed to disk, and unlocked + * non-zero - Failure, errors added to err + */ +int +close_keystore(PKG_ERR *err, keystore_handle_t keystore_h, + keystore_passphrase_cb cb) +{ + int ret = 0; + keystore_t *keystore = keystore_h; + + if (keystore->dirty) { + /* write out the keystore first */ + if (!write_keystore(err, keystore, cb)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->path); + ret = 1; + goto cleanup; + } + } + + if (!unlock_keystore(err, keystore)) { + pkgerr_add(err, PKGERR_UNLOCK, gettext(ERR_KEYSTORE_UNLOCK), + keystore->path); + ret = 1; + goto cleanup; + } + + free_keystore(keystore); +cleanup: + return (ret); +} + +/* + * merge_ca_cert - Adds a trusted certificate (trust anchor) to a keystore. + * certificate checked for validity dates and non-duplicity. + * + * Arguments: + * err - Error object to add errors to + * cacert - Certificate which to merge into keystore + * keystore - The keystore into which the certificate is merged + * + * Returns: + * 0 - Success - Certificate passes validity, and + * is merged into keystore + * non-zero - Failure, errors recorded in err + */ +int +merge_ca_cert(PKG_ERR *err, X509 *cacert, keystore_handle_t keystore_h) +{ + + int ret = 0; + X509 *existing = NULL; + char *fname; + keystore_t *keystore = keystore_h; + + /* check validity dates */ + if (check_cert(err, cacert) != 0) { + ret = 1; + goto cleanup; + } + + /* create the certificate's friendlyName */ + fname = get_subject_display_name(cacert); + + if (sunw_set_fname(fname, NULL, cacert) != 0) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + + /* merge certificate into the keystore */ + if (keystore->cacerts == NULL) { + /* no existing truststore, so make a new one */ + if ((keystore->cacerts = sk_X509_new_null()) == NULL) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + } else { + /* existing truststore, make sure there's no duplicate */ + if (sunw_find_fname(fname, NULL, keystore->cacerts, + NULL, &existing) < 0) { + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), + __FILE__, __LINE__); + ERR_print_errors_fp(stderr); + ret = 1; + goto cleanup; + /* could not search properly! */ + } + if (existing != NULL) { + /* whoops, found one already */ + pkgerr_add(err, PKGERR_DUPLICATE, + gettext(ERR_KEYSTORE_DUPLICATECERT), fname); + ret = 1; + goto cleanup; + } + } + + (void) sk_X509_push(keystore->cacerts, cacert); + keystore->dirty = B_TRUE; +cleanup: + if (existing != NULL) + X509_free(existing); + return (ret); +} + +/* + * find_key_cert_pair - Searches a keystore for a matching + * public key certificate and private key, given an alias. + * + * Arguments: + * err - Error object to add errors to + * ks - Keystore to search + * alias - Name to used to match certificate's alias + * key - Resulting key is placed here + * cert - Resulting cert is placed here + * + * Returns: + * 0 - Success - Matching cert/key pair placed in key and cert. + * non-zero - Failure, errors recorded in err + */ +int +find_key_cert_pair(PKG_ERR *err, keystore_handle_t ks_h, char *alias, + EVP_PKEY **key, X509 **cert) +{ + X509 *tmpcert = NULL; + EVP_PKEY *tmpkey = NULL; + int ret = 0; + int items_found; + keystore_t *ks = ks_h; + + if (key == NULL || cert == NULL) { + pkgerr_add(err, PKGERR_NOPUBKEY, + gettext(ERR_KEYSTORE_NOPUBCERTS), ks->path); + ret = 1; + goto cleanup; + } + + if (ks->clcerts == NULL) { + /* no public certs */ + pkgerr_add(err, PKGERR_NOPUBKEY, + gettext(ERR_KEYSTORE_NOCERTS), ks->path); + ret = 1; + goto cleanup; + } + if (ks->pkeys == NULL) { + /* no private keys */ + pkgerr_add(err, PKGERR_NOPRIVKEY, + gettext(ERR_KEYSTORE_NOKEYS), ks->path); + ret = 1; + goto cleanup; + } + + /* try the easy case first */ + if ((sk_EVP_PKEY_num(ks->pkeys) == 1) && + (sk_X509_num(ks->clcerts) == 1)) { + tmpkey = sk_EVP_PKEY_value(ks->pkeys, 0); + tmpcert = sk_X509_value(ks->clcerts, 0); + if (sunw_check_keys(tmpcert, tmpkey)) { + /* + * only one private key and public key cert, and they + * match, so use them + */ + *key = tmpkey; + tmpkey = NULL; + *cert = tmpcert; + tmpcert = NULL; + goto cleanup; + } + } + + /* Attempt to find the right pair given the alias */ + items_found = sunw_find_fname(alias, ks->pkeys, ks->clcerts, + &tmpkey, &tmpcert); + + if ((items_found < 0) || + (items_found & (FOUND_PKEY | FOUND_CERT)) == 0) { + /* no key/cert pair found. bail. */ + pkgerr_add(err, PKGERR_BADALIAS, + gettext(ERR_KEYSTORE_NOMATCH), alias); + ret = 1; + goto cleanup; + } + + /* success */ + *key = tmpkey; + tmpkey = NULL; + *cert = tmpcert; + tmpcert = NULL; + +cleanup: + + if (tmpcert != NULL) + (void) X509_free(tmpcert); + + if (tmpkey != NULL) + sunw_evp_pkey_free(tmpkey); + + return (ret); +} + +/* + * find_ca_certs - Searches a keystore for trusted certificates + * + * Arguments: + * err - Error object to add errors to + * ks - Keystore to search + * cacerts - resulting set of trusted certs are placed here + * + * Returns: + * 0 - Success - trusted cert list returned in cacerts + * non-zero - Failure, errors recorded in err + */ +int +find_ca_certs(PKG_ERR *err, keystore_handle_t ks_h, STACK_OF(X509) **cacerts) +{ + + keystore_t *ks = ks_h; + + /* easy */ + if (cacerts == NULL) { + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), __FILE__, __LINE__); + return (1); + } + + *cacerts = ks->cacerts; + return (0); +} + +/* + * find_cl_certs - Searches a keystore for user certificates + * + * Arguments: + * err - Error object to add errors to + * ks - Keystore to search + * cacerts - resulting set of user certs are placed here + * + * No matching of any kind is performed. + * Returns: + * 0 - Success - trusted cert list returned in cacerts + * non-zero - Failure, errors recorded in err + */ +/* ARGSUSED */ +int +find_cl_certs(PKG_ERR *err, keystore_handle_t ks_h, STACK_OF(X509) **clcerts) +{ + keystore_t *ks = ks_h; + + /* easy */ + *clcerts = ks->clcerts; + return (0); +} + + +/* + * merge_cert_and_key - Adds a user certificate and matching + * private key to a keystore. + * certificate checked for validity dates and non-duplicity. + * + * Arguments: + * err - Error object to add errors to + * cert - Certificate which to merge into keystore + * key - matching private key to 'cert' + * alias - Name which to store the cert and key under + * keystore - The keystore into which the certificate is merged + * + * Returns: + * 0 - Success - Certificate passes validity, and + * is merged into keystore, along with key + * non-zero - Failure, errors recorded in err + */ +int +merge_cert_and_key(PKG_ERR *err, X509 *cert, EVP_PKEY *key, char *alias, + keystore_handle_t keystore_h) +{ + X509 *existingcert = NULL; + EVP_PKEY *existingkey = NULL; + int ret = 0; + keystore_t *keystore = keystore_h; + + /* check validity dates */ + if (check_cert(err, cert) != 0) { + ret = 1; + goto cleanup; + } + + /* set the friendlyName of the key and cert to the supplied alias */ + if (sunw_set_fname(alias, key, cert) != 0) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + + /* merge certificate and key into the keystore */ + if (keystore->clcerts == NULL) { + /* no existing truststore, so make a new one */ + if ((keystore->clcerts = sk_X509_new_null()) == NULL) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + } else { + /* existing certstore, make sure there's no duplicate */ + if (sunw_find_fname(alias, NULL, keystore->clcerts, + NULL, &existingcert) < 0) { + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), + __FILE__, __LINE__); + ERR_print_errors_fp(stderr); + ret = 1; + goto cleanup; + /* could not search properly! */ + } + if (existingcert != NULL) { + /* whoops, found one already */ + pkgerr_add(err, PKGERR_DUPLICATE, + gettext(ERR_KEYSTORE_DUPLICATECERT), alias); + ret = 1; + goto cleanup; + } + } + + if (keystore->pkeys == NULL) { + /* no existing keystore, so make a new one */ + if ((keystore->pkeys = sk_EVP_PKEY_new_null()) == NULL) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + } else { + /* existing keystore, so make sure there's no duplicate entry */ + if (sunw_find_fname(alias, keystore->pkeys, NULL, + &existingkey, NULL) < 0) { + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), + __FILE__, __LINE__); + ERR_print_errors_fp(stderr); + ret = 1; + goto cleanup; + /* could not search properly! */ + } + if (existingkey != NULL) { + /* whoops, found one already */ + pkgerr_add(err, PKGERR_DUPLICATE, + gettext(ERR_KEYSTORE_DUPLICATEKEY), alias); + ret = 1; + goto cleanup; + } + } + + (void) sk_X509_push(keystore->clcerts, cert); + (void) sk_EVP_PKEY_push(keystore->pkeys, key); + keystore->dirty = B_TRUE; +cleanup: + if (existingcert != NULL) + (void) X509_free(existingcert); + if (existingkey != NULL) + (void) sunw_evp_pkey_free(existingkey); + return (ret); +} + +/* + * delete_cert_and_keys - Deletes one or more certificates + * and matching private keys from a keystore. + * + * Arguments: + * err - Error object to add errors to + * ks - The keystore from which certs and keys are deleted + * alias - Name which to search for certificates and keys + * to delete + * + * Returns: + * 0 - Success - All trusted certs which match 'alias' + * are deleted. All user certificates + * which match 'alias' are deleted, along + * with the matching private key. + * non-zero - Failure, errors recorded in err + */ +int +delete_cert_and_keys(PKG_ERR *err, keystore_handle_t ks_h, char *alias) +{ + X509 *existingcert; + EVP_PKEY *existingkey; + int i; + char *fname = NULL; + boolean_t found = B_FALSE; + keystore_t *ks = ks_h; + + /* delete any and all client certs with the supplied name */ + if (ks->clcerts != NULL) { + for (i = 0; i < sk_X509_num(ks->clcerts); i++) { + existingcert = sk_X509_value(ks->clcerts, i); + if (sunw_get_cert_fname(GETDO_COPY, + existingcert, &fname) >= 0) { + if (streq(fname, alias)) { + /* match, so nuke it */ + existingcert = + sk_X509_delete(ks->clcerts, i); + X509_free(existingcert); + existingcert = NULL; + found = B_TRUE; + } + (void) OPENSSL_free(fname); + fname = NULL; + } + } + if (sk_X509_num(ks->clcerts) <= 0) { + /* we deleted all the client certs */ + sk_X509_free(ks->clcerts); + ks->clcerts = NULL; + } + } + + /* and now the private keys */ + if (ks->pkeys != NULL) { + for (i = 0; i < sk_EVP_PKEY_num(ks->pkeys); i++) { + existingkey = sk_EVP_PKEY_value(ks->pkeys, i); + if (sunw_get_pkey_fname(GETDO_COPY, + existingkey, &fname) >= 0) { + if (streq(fname, alias)) { + /* match, so nuke it */ + existingkey = + sk_EVP_PKEY_delete(ks->pkeys, i); + sunw_evp_pkey_free(existingkey); + existingkey = NULL; + found = B_TRUE; + } + (void) OPENSSL_free(fname); + fname = NULL; + } + } + if (sk_EVP_PKEY_num(ks->pkeys) <= 0) { + /* we deleted all the private keys */ + sk_EVP_PKEY_free(ks->pkeys); + ks->pkeys = NULL; + } + } + + /* finally, remove any trust anchors that match */ + + if (ks->cacerts != NULL) { + for (i = 0; i < sk_X509_num(ks->cacerts); i++) { + existingcert = sk_X509_value(ks->cacerts, i); + if (sunw_get_cert_fname(GETDO_COPY, + existingcert, &fname) >= 0) { + if (streq(fname, alias)) { + /* match, so nuke it */ + existingcert = + sk_X509_delete(ks->cacerts, i); + X509_free(existingcert); + existingcert = NULL; + found = B_TRUE; + } + (void) OPENSSL_free(fname); + fname = NULL; + } + } + if (sk_X509_num(ks->cacerts) <= 0) { + /* we deleted all the CA certs */ + sk_X509_free(ks->cacerts); + ks->cacerts = NULL; + } + } + + if (found) { + ks->dirty = B_TRUE; + return (0); + } else { + /* no certs or keys deleted */ + pkgerr_add(err, PKGERR_NOALIASMATCH, + gettext(ERR_KEYSTORE_NOCERTKEY), + alias, ks->path); + return (1); + } +} + +/* + * check_cert - Checks certificate validity. This routine + * checks that the current time falls within the period + * of validity for the cert. + * + * Arguments: + * err - Error object to add errors to + * cert - The certificate to check + * + * Returns: + * 0 - Success - Certificate checks out + * non-zero - Failure, errors and reasons recorded in err + */ +int +check_cert(PKG_ERR *err, X509 *cert) +{ + char currtimestr[ATTR_MAX]; + time_t currtime; + char *r, *before_str, *after_str; + /* get current time */ + if ((currtime = time(NULL)) == (time_t)-1) { + pkgerr_add(err, PKGERR_TIME, gettext(ERR_CURR_TIME)); + return (1); + } + + (void) strlcpy(currtimestr, ctime(&currtime), ATTR_MAX); + + /* trim whitespace from end of time string */ + for (r = (currtimestr + strlen(currtimestr) - 1); isspace(*r); r--) { + *r = '\0'; + } + /* check validity of cert */ + switch (sunw_check_cert_times(CHK_BOTH, cert)) { + case CHKERR_TIME_OK: + /* Current time meets requested checks */ + break; + case CHKERR_TIME_BEFORE_BAD: + /* 'not before' field is invalid */ + case CHKERR_TIME_AFTER_BAD: + /* 'not after' field is invalid */ + pkgerr_add(err, PKGERR_TIME, gettext(ERR_CERT_TIME_BAD)); + return (1); + case CHKERR_TIME_IS_BEFORE: + /* Current time is before 'not before' */ + case CHKERR_TIME_HAS_EXPIRED: + /* + * Ignore expiration time since the trust cert used to + * verify the certs used to sign Sun patches is already + * expired. Once the patches get resigned with the new + * cert we will check expiration against the time the + * patch was signed and not the time it is installed. + */ + return (0); + default: + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), + __FILE__, __LINE__); + return (1); + } + + /* all checks ok */ + return (0); +} + +/* + * check_cert - Checks certificate validity. This routine + * checks everything that check_cert checks, and additionally + * verifies that the private key and corresponding public + * key are indeed a pair. + * + * Arguments: + * err - Error object to add errors to + * cert - The certificate to check + * key - the key to check + * Returns: + * 0 - Success - Certificate checks out + * non-zero - Failure, errors and reasons recorded in err + */ +int +check_cert_and_key(PKG_ERR *err, X509 *cert, EVP_PKEY *key) +{ + + /* check validity dates */ + if (check_cert(err, cert) != 0) { + return (1); + } + + /* check key pair match */ + if (sunw_check_keys(cert, key) == 0) { + pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MISMATCHED_KEYS), + get_subject_display_name(cert)); + return (1); + } + + /* all checks OK */ + return (0); +} + +/* ------------------ private functions ---------------------- */ + +/* + * verify_keystore_integrity - Searches for the remnants + * of a failed or aborted keystore modification, and + * cleans up the files, retstores the keystore to a known + * state. + * + * Arguments: + * err - Error object to add errors to + * keystore_file - Base directory or filename of keystore + * app - Application making request + * + * Returns: + * 0 - Success - Keystore is restored, or untouched in the + * case that cleanup was unnecessary + * non-zero - Failure, errors and reasons recorded in err + */ +static boolean_t +verify_keystore_integrity(PKG_ERR *err, keystore_t *keystore) +{ + if (keystore->capath != NULL) { + if (!restore_keystore_file(err, keystore->capath)) { + return (B_FALSE); + } + } + if (keystore->clpath != NULL) { + if (!restore_keystore_file(err, keystore->clpath)) { + return (B_FALSE); + } + } + if (keystore->keypath != NULL) { + if (!restore_keystore_file(err, keystore->keypath)) { + return (B_FALSE); + } + } + return (B_TRUE); +} + +/* + * restore_keystore_file - restores a keystore file to + * a known state. + * + * Keystore files can possibly be corrupted by a variety + * of error conditions during reading/writing. This + * routine, along with write_keystore_file, tries to + * maintain keystore integrity by writing the files + * out in a particular order, minimizing the time period + * that the keystore is in an indeterminate state. + * + * With the current implementation, there are some failures + * that are wholly unrecoverable, such as disk corruption. + * These routines attempt to minimize the risk, but not + * eliminate it. When better, atomic operations are available + * (such as a trued atabase with commit, rollback, and + * guaranteed atomicity), this implementation should use that. + * + * Arguments: + * err - Error object to add errors to + * keystore_file - keystore file path to restore. + * + * Returns: + * 0 - Success - Keystore file is restored, or untouched in the + * case that cleanup was unnecessary + * non-zero - Failure, errors and reasons recorded in err + */ +/* ARGSUSED */ +static boolean_t +restore_keystore_file(PKG_ERR *err, char *keystore_file) +{ + char newpath[MAXPATHLEN]; + char backuppath[MAXPATHLEN]; + int newfd; + struct stat buf; + int len; + + if (((len = snprintf(newpath, MAXPATHLEN, "%s.new", + keystore_file)) < 0) || + (len >= ATTR_MAX)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), keystore_file); + return (B_FALSE); + } + + if (((len = snprintf(backuppath, MAXPATHLEN, "%s.bak", + keystore_file)) < 0) || + (len >= ATTR_MAX)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), keystore_file); + return (B_FALSE); + } + + if ((newfd = open(newpath, O_RDWR|O_NONBLOCK, 0)) != -1) { + if (fstat(newfd, &buf) != -1) { + if (S_ISREG(buf.st_mode)) { + /* + * restore the file, waiting on it + * to be free for locking, or for + * it to disappear + */ + if (!wait_restore(newfd, keystore_file, + newpath, backuppath)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_WRITE), + newpath, strerror(errno)); + (void) close(newfd); + return (B_FALSE); + } else { + return (B_TRUE); + } + } else { + /* "new" file is not a regular file */ + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_NOT_REG), newpath); + (void) close(newfd); + return (B_FALSE); + } + } else { + /* couldn't stat "new" file */ + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_WRITE), newpath, + strerror(errno)); + (void) close(newfd); + return (B_FALSE); + } + } else { + /* "new" file doesn't exist */ + return (B_TRUE); + } +} + +static boolean_t +wait_restore(int newfd, char *keystore_file, + char *origpath, char *backuppath) +{ + struct stat buf; + FILE *newstream; + PKCS12 *p12; + + (void) alarm(LOCK_TIMEOUT); + if (file_lock(newfd, F_WRLCK, 1) == -1) { + /* could not lock file */ + (void) alarm(0); + return (B_FALSE); + } + (void) alarm(0); + + if (fstat(newfd, &buf) != -1) { + if (S_ISREG(buf.st_mode)) { + /* + * The new file still + * exists, with no + * owner. It must be + * the result of an + * aborted update. + */ + newstream = fdopen(newfd, "r"); + if ((p12 = + d2i_PKCS12_fp(newstream, + NULL)) != NULL) { + /* + * The file + * appears + * complete. + * Replace the + * exsisting + * keystore + * file with + * this one + */ + (void) rename(keystore_file, backuppath); + (void) rename(origpath, keystore_file); + PKCS12_free(p12); + } else { + /* The file is not complete. Remove it */ + (void) remove(origpath); + } + /* remove backup file */ + (void) remove(backuppath); + (void) fclose(newstream); + (void) close(newfd); + return (B_TRUE); + } else { + /* + * new file exists, but is not a + * regular file + */ + (void) close(newfd); + return (B_FALSE); + } + } else { + /* + * could not stat file. Unless + * the reason was that the file + * is now gone, this is an error + */ + if (errno != ENOENT) { + (void) close(newfd); + return (B_FALSE); + } + /* + * otherwise, file is gone. The process + * that held the lock must have + * successfully cleaned up and + * exited with a valid keystore + * state + */ + (void) close(newfd); + return (B_TRUE); + } +} + +/* + * resolve_paths - figure out if we are dealing with a single-file + * or multi-file keystore + * + * The flags tell resolve_paths how to behave: + * + * KEYSTORE_PATH_SOFT + * If the keystore file does not exist at <base>/<app> then + * use <base> as the path to the keystore. This can be used, + * for example, to access an app-specific keystore iff it + * exists, otherwise revert back to an app-generic keystore. + * + * KEYSTORE_PATH_HARD + * Always use the keystore located at <keystore_path>/<app>. + * In read/write mode, if the files do not exist, then + * they will be created. This is used to avoid falling + * back to an app-generic keystore path when the app-specific + * one does not exist. + * + * Arguments: + * err - Error object to add errors to + * keystore_file - base keystore file path to lock + * app - Application making requests + * flags - Control flags (see above description) + * keystore - object which is being locked + * + * Returns: + * B_TRUE - Success - Keystore file is locked, paths to + * appropriate files placed in keystore. + * B_FALSE - Failure, errors and reasons recorded in err + */ +static boolean_t +resolve_paths(PKG_ERR *err, char *keystore_file, char *app, + long flags, keystore_t *keystore) +{ + char storepath[PATH_MAX]; + struct stat buf; + boolean_t multi = B_FALSE; + int fd1, fd2, len; + + /* + * figure out whether we are dealing with a single-file keystore + * or a multi-file keystore + */ + if (app != NULL) { + if (((len = snprintf(storepath, PATH_MAX, "%s/%s", + keystore_file, app)) < 0) || + (len >= ATTR_MAX)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), + keystore_file); + return (B_FALSE); + } + + if (((fd1 = open(storepath, O_NONBLOCK|O_RDONLY)) == -1) || + (fstat(fd1, &buf) == -1) || + !S_ISDIR(buf.st_mode)) { + /* + * app-specific does not exist + * fallback to app-generic, if flags say we can + */ + if ((flags & KEYSTORE_PATH_MASK) == + KEYSTORE_PATH_SOFT) { + + if (((fd2 = open(keystore_file, + O_NONBLOCK|O_RDONLY)) != -1) && + (fstat(fd2, &buf) != -1)) { + if (S_ISDIR(buf.st_mode)) { + /* + * app-generic dir + * exists, so use it + * as a multi-file + * keystore + */ + multi = B_TRUE; + app = NULL; + } else if (S_ISREG(buf.st_mode)) { + /* + * app-generic file exists, so + * use it as a single file ks + */ + multi = B_FALSE; + app = NULL; + } + } + } + } + if (fd1 != -1) + (void) close(fd1); + if (fd2 != -1) + (void) close(fd2); + } else { + if (((fd1 = open(keystore_file, + O_NONBLOCK|O_RDONLY)) != -1) && + (fstat(fd1, &buf) != -1) && + S_ISDIR(buf.st_mode)) { + /* + * app-generic dir exists, so use + * it as a multi-file keystore + */ + multi = B_TRUE; + } + if (fd1 != -1) + (void) close(fd1); + } + + if (app != NULL) { + /* app-specific keystore */ + (void) snprintf(storepath, PATH_MAX, "%s/%s/%s", + keystore_file, app, TRUSTSTORE); + keystore->capath = xstrdup(storepath); + (void) snprintf(storepath, PATH_MAX, "%s/%s/%s", + keystore_file, app, CERTSTORE); + keystore->clpath = xstrdup(storepath); + (void) snprintf(storepath, PATH_MAX, "%s/%s/%s", + keystore_file, app, KEYSTORE); + keystore->keypath = xstrdup(storepath); + } else { + /* app-generic keystore */ + if (!multi) { + /* single-file app-generic keystore */ + keystore->capath = xstrdup(keystore_file); + keystore->keypath = NULL; + keystore->clpath = NULL; + } else { + /* multi-file app-generic keystore */ + (void) snprintf(storepath, PATH_MAX, "%s/%s", + keystore_file, TRUSTSTORE); + keystore->capath = xstrdup(storepath); + (void) snprintf(storepath, PATH_MAX, "%s/%s", + keystore_file, CERTSTORE); + keystore->clpath = xstrdup(storepath); + (void) snprintf(storepath, PATH_MAX, "%s/%s", + keystore_file, KEYSTORE); + keystore->keypath = xstrdup(storepath); + } + } + + return (B_TRUE); +} + +/* + * lock_keystore - Locks a keystore for shared (read-only) + * or exclusive (read-write) access. + * + * The flags tell lock_keystore how to behave: + * + * KEYSTORE_ACCESS_READONLY + * opens keystore read-only. Attempts to modify results in an error + * + * KEYSTORE_ACCESS_READWRITE + * opens keystore read-write + * + * KEYSTORE_PATH_SOFT + * If the keystore file does not exist at <base>/<app> then + * use <base> as the path to the keystore. This can be used, + * for example, to access an app-specific keystore iff it + * exists, otherwise revert back to an app-generic keystore. + * + * KEYSTORE_PATH_HARD + * Always use the keystore located at <keystore_path>/<app>. + * In read/write mode, if the files do not exist, then + * they will be created. This is used to avoid falling + * back to an app-generic keystore path when the app-specific + * one does not exist. + * + * Arguments: + * err - Error object to add errors to + * flags - Control flags (see above description) + * keystore - object which is being locked + * + * Returns: + * 0 - Success - Keystore file is locked, paths to + * appropriate files placed in keystore. + * non-zero - Failure, errors and reasons recorded in err + */ +static boolean_t +lock_keystore(PKG_ERR *err, long flags, keystore_t *keystore) +{ + boolean_t ret = B_TRUE; + struct stat buf; + + switch (flags & KEYSTORE_ACCESS_MASK) { + case KEYSTORE_ACCESS_READONLY: + if ((keystore->cafd = + open(keystore->capath, O_NONBLOCK|O_RDONLY)) == -1) { + if (errno == ENOENT) { + /* + * no keystore. try to create an + * empty one so we can lock on it and + * prevent others from gaining + * exclusive access. It will be + * deleted when the keystore is closed. + */ + if ((keystore->cafd = + open(keystore->capath, + O_NONBLOCK|O_RDWR|O_CREAT|O_EXCL, + S_IRUSR|S_IWUSR)) == -1) { + pkgerr_add(err, PKGERR_READ, + gettext(ERR_NO_KEYSTORE), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + pkgerr_add(err, PKGERR_READ, + gettext(ERR_KEYSTORE_OPEN), + keystore->capath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + } + if (fstat(keystore->cafd, &buf) != -1) { + if (S_ISREG(buf.st_mode)) { + if (file_lock(keystore->cafd, F_RDLCK, + 0) == -1) { + pkgerr_add(err, PKGERR_LOCKED, + gettext(ERR_KEYSTORE_LOCKED_READ), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + /* ca file not a regular file! */ + pkgerr_add(err, PKGERR_READ, + gettext(ERR_NOT_REG), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + pkgerr_add(err, PKGERR_READ, + gettext(ERR_KEYSTORE_OPEN), + keystore->capath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + break; + case KEYSTORE_ACCESS_READWRITE: + + if ((keystore->cafd = open(keystore->capath, + O_RDWR|O_NONBLOCK)) == -1) { + /* does not exist. try to create an empty one */ + if (errno == ENOENT) { + if ((keystore->cafd = + open(keystore->capath, + O_NONBLOCK|O_RDWR|O_CREAT|O_EXCL, + S_IRUSR|S_IWUSR)) == -1) { + pkgerr_add(err, PKGERR_READ, + gettext(ERR_KEYSTORE_WRITE), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + pkgerr_add(err, PKGERR_READ, + gettext(ERR_KEYSTORE_OPEN), + keystore->capath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + } + if (fstat(keystore->cafd, &buf) != -1) { + if (S_ISREG(buf.st_mode)) { + if (file_lock(keystore->cafd, F_WRLCK, + 0) == -1) { + pkgerr_add(err, PKGERR_LOCKED, + gettext(ERR_KEYSTORE_LOCKED), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + /* ca file not a regular file! */ + pkgerr_add(err, PKGERR_READ, + gettext(ERR_NOT_REG), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + pkgerr_add(err, PKGERR_READ, + gettext(ERR_KEYSTORE_OPEN), + keystore->capath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + + break; + default: + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_KEYSTORE_INTERNAL), + __FILE__, __LINE__); + ret = B_FALSE; + goto cleanup; + } + +cleanup: + if (!ret) { + if (keystore->cafd > 0) { + (void) file_unlock(keystore->cafd); + (void) close(keystore->cafd); + keystore->cafd = -1; + } + + if (keystore->capath != NULL) + free(keystore->capath); + if (keystore->clpath != NULL) + free(keystore->clpath); + if (keystore->keypath != NULL) + free(keystore->keypath); + keystore->capath = NULL; + keystore->clpath = NULL; + keystore->keypath = NULL; + } + + return (ret); +} + +/* + * unlock_keystore - Unocks a keystore + * + * Arguments: + * err - Error object to add errors to + * keystore - keystore object to unlock + * Returns: + * 0 - Success - Keystore files are unlocked, files are closed, + * non-zero - Failure, errors and reasons recorded in err + */ +/* ARGSUSED */ +static boolean_t +unlock_keystore(PKG_ERR *err, keystore_t *keystore) +{ + + /* + * Release lock on the CA file. + * Delete file if it is empty + */ + if (file_empty(keystore->capath)) { + (void) remove(keystore->capath); + } + + (void) file_unlock(keystore->cafd); + (void) close(keystore->cafd); + return (B_TRUE); +} + +/* + * read_keystore - Reads keystore files of disk, parses + * into internal structures. + * + * Arguments: + * err - Error object to add errors to + * keystore - keystore object to read into + * cb - callback to get password, if required + * Returns: + * 0 - Success - Keystore files are read, and placed + * into keystore structure. + * non-zero - Failure, errors and reasons recorded in err + */ +static boolean_t +read_keystore(PKG_ERR *err, keystore_t *keystore, keystore_passphrase_cb cb) +{ + boolean_t ret = B_TRUE; + PKCS12 *p12 = NULL; + boolean_t ca_empty; + boolean_t have_passwd = B_FALSE; + boolean_t cl_empty = B_TRUE; + boolean_t key_empty = B_TRUE; + + ca_empty = file_empty(keystore->capath); + + if (keystore->clpath != NULL) + cl_empty = file_empty(keystore->clpath); + if (keystore->keypath != NULL) + key_empty = file_empty(keystore->keypath); + + if (ca_empty && cl_empty && key_empty) { + keystore->new = B_TRUE; + } + + if (!ca_empty) { + /* first read the ca file */ + if ((p12 = read_keystore_file(err, + keystore->capath)) == NULL) { + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), keystore->capath); + ret = B_FALSE; + goto cleanup; + } + + /* Get password, using callback if necessary */ + if (!have_passwd) { + if (!get_keystore_passwd(err, p12, cb, keystore)) { + ret = B_FALSE; + goto cleanup; + } + have_passwd = B_TRUE; + } + + /* decrypt and parse keystore file */ + if (sunw_PKCS12_contents(p12, keystore->passphrase, + &keystore->pkeys, &keystore->cacerts) < 0) { + /* could not parse the contents */ + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), keystore->capath); + ret = B_FALSE; + goto cleanup; + } + + PKCS12_free(p12); + p12 = NULL; + } else { + + /* + * truststore is empty, so we don't have any trusted + * certs + */ + keystore->cacerts = NULL; + } + + /* + * if there is no cl file or key file, use the cl's and key's found + * in the ca file + */ + if (keystore->clpath == NULL && !ca_empty) { + if (sunw_split_certs(keystore->pkeys, keystore->cacerts, + &keystore->clcerts, NULL) < 0) { + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + /* + * files are in separate files. read keys out of the keystore + * certs out of the certstore, if they are not empty + */ + if (!cl_empty) { + if ((p12 = read_keystore_file(err, + keystore->clpath)) == NULL) { + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), + keystore->clpath); + ret = B_FALSE; + goto cleanup; + } + + /* Get password, using callback if necessary */ + if (!have_passwd) { + if (!get_keystore_passwd(err, p12, cb, + keystore)) { + ret = B_FALSE; + goto cleanup; + } + have_passwd = B_TRUE; + } + + if (check_password(p12, + keystore->passphrase) == B_FALSE) { + /* + * password in client cert file + * is different than + * the one in the other files! + */ + pkgerr_add(err, PKGERR_BADPASS, + gettext(ERR_MISMATCHPASS), + keystore->clpath, + keystore->capath, keystore->path); + ret = B_FALSE; + goto cleanup; + } + + if (sunw_PKCS12_contents(p12, keystore->passphrase, + NULL, &keystore->clcerts) < 0) { + /* could not parse the contents */ + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), + keystore->clpath); + ret = B_FALSE; + goto cleanup; + } + + PKCS12_free(p12); + p12 = NULL; + } else { + keystore->clcerts = NULL; + } + + if (!key_empty) { + if ((p12 = read_keystore_file(err, + keystore->keypath)) == NULL) { + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), + keystore->keypath); + ret = B_FALSE; + goto cleanup; + } + + /* Get password, using callback if necessary */ + if (!have_passwd) { + if (!get_keystore_passwd(err, p12, cb, + keystore)) { + ret = B_FALSE; + goto cleanup; + } + have_passwd = B_TRUE; + } + + if (check_password(p12, + keystore->passphrase) == B_FALSE) { + pkgerr_add(err, PKGERR_BADPASS, + gettext(ERR_MISMATCHPASS), + keystore->keypath, + keystore->capath, keystore->path); + ret = B_FALSE; + goto cleanup; + } + + if (sunw_PKCS12_contents(p12, keystore->passphrase, + &keystore->pkeys, NULL) < 0) { + /* could not parse the contents */ + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), + keystore->keypath); + ret = B_FALSE; + goto cleanup; + } + + PKCS12_free(p12); + p12 = NULL; + } else { + keystore->pkeys = NULL; + } + } + +cleanup: + if (p12 != NULL) + PKCS12_free(p12); + return (ret); +} + +/* + * get_keystore_password - retrieves pasword used to + * decrypt PKCS12 structure. + * + * Arguments: + * err - Error object to add errors to + * p12 - PKCS12 structure which returned password should + * decrypt + * cb - callback to collect password. + * keystore - The keystore in which the PKCS12 structure + * will eventually populate. + * Returns: + * B_TRUE - success. + * keystore password is set in keystore->passphrase. + * B_FALSE - failure, errors logged + */ +static boolean_t +get_keystore_passwd(PKG_ERR *err, PKCS12 *p12, keystore_passphrase_cb cb, + keystore_t *keystore) +{ + char *passwd; + char passbuf[KEYSTORE_PASS_MAX + 1]; + keystore_passphrase_data data; + + /* see if no password is the right password */ + if (check_password(p12, "") == B_TRUE) { + passwd = ""; + } else if (check_password(p12, NULL) == B_TRUE) { + passwd = NULL; + } else { + /* oops, it's encrypted. get password */ + data.err = err; + if (cb(passbuf, KEYSTORE_PASS_MAX, 0, + &data) == -1) { + /* could not get password */ + return (B_FALSE); + } + + if (check_password(p12, passbuf) == B_FALSE) { + /* wrong password */ + pkgerr_add(err, PKGERR_BADPASS, + gettext(ERR_BADPASS)); + return (B_FALSE); + } + + /* + * make copy of password buffer, since it + * goes away upon return + */ + passwd = xstrdup(passbuf); + } + keystore->passphrase = passwd; + return (B_TRUE); +} + +/* + * write_keystore - Writes keystore files to disk + * + * Arguments: + * err - Error object to add errors to + * keystore - keystore object to write from + * passwd - password used to encrypt keystore + * Returns: + * 0 - Success - Keystore contents are written out to + * the same locations as read from + * non-zero - Failure, errors and reasons recorded in err + */ +static boolean_t +write_keystore(PKG_ERR *err, keystore_t *keystore, + keystore_passphrase_cb cb) +{ + PKCS12 *p12 = NULL; + boolean_t ret = B_TRUE; + keystore_passphrase_data data; + char passbuf[KEYSTORE_PASS_MAX + 1]; + + if (keystore->capath != NULL && keystore->clpath == NULL && + keystore->keypath == NULL) { + + /* + * keystore is a file. + * just write out a single file + */ + if ((keystore->pkeys == NULL) && + (keystore->clcerts == NULL) && + (keystore->cacerts == NULL)) { + if (!clear_keystore_file(err, keystore->capath)) { + /* + * no keys or certs to write out, so + * blank the ca file. we do not + * delete it since it is used as a + * lock by lock_keystore() in + * subsequent invocations + */ + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } else { + /* + * if the keystore is being created for the first time, + * prompt for a passphrase for encryption + */ + if (keystore->new) { + data.err = err; + if (cb(passbuf, KEYSTORE_PASS_MAX, + 1, &data) == -1) { + ret = B_FALSE; + goto cleanup; + } + } else { + /* + * use the one used when the keystore + * was read + */ + strlcpy(passbuf, keystore->passphrase, + KEYSTORE_PASS_MAX); + } + + p12 = sunw_PKCS12_create(passbuf, keystore->pkeys, + keystore->clcerts, keystore->cacerts); + + if (p12 == NULL) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_FORM), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + + if (!write_keystore_file(err, keystore->capath, p12)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } + + } else { + /* files are seprate. Do one at a time */ + + /* + * if the keystore is being created for the first time, + * prompt for a passphrase for encryption + */ + if (keystore->new && ((keystore->pkeys != NULL) || + (keystore->clcerts != NULL) || + (keystore->cacerts != NULL))) { + data.err = err; + if (cb(passbuf, KEYSTORE_PASS_MAX, + 1, &data) == -1) { + ret = B_FALSE; + goto cleanup; + } + } else { + /* use the one used when the keystore was read */ + strlcpy(passbuf, keystore->passphrase, + KEYSTORE_PASS_MAX); + } + + /* do private keys first */ + if (keystore->pkeys != NULL) { + p12 = sunw_PKCS12_create(passbuf, keystore->pkeys, + NULL, NULL); + + if (p12 == NULL) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_FORM), + keystore->keypath); + ret = B_FALSE; + goto cleanup; + } + + if (!write_keystore_file(err, keystore->keypath, + p12)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->keypath); + ret = B_FALSE; + goto cleanup; + } + + PKCS12_free(p12); + } else { + if ((remove(keystore->keypath) != 0) && + (errno != ENOENT)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_REMOVE), + keystore->keypath); + ret = B_FALSE; + goto cleanup; + } + } + + /* do user certs next */ + if (keystore->clcerts != NULL) { + p12 = sunw_PKCS12_create(passbuf, NULL, + keystore->clcerts, NULL); + + if (p12 == NULL) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_FORM), + keystore->clpath); + ret = B_FALSE; + goto cleanup; + } + + if (!write_keystore_file(err, keystore->clpath, p12)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->clpath); + ret = B_FALSE; + goto cleanup; + } + + PKCS12_free(p12); + } else { + if ((remove(keystore->clpath) != 0) && + (errno != ENOENT)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_REMOVE), + keystore->clpath); + ret = B_FALSE; + goto cleanup; + } + } + + + /* finally do CA cert file */ + if (keystore->cacerts != NULL) { + p12 = sunw_PKCS12_create(passbuf, NULL, + NULL, keystore->cacerts); + + if (p12 == NULL) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_FORM), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + + if (!write_keystore_file(err, keystore->capath, p12)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + + PKCS12_free(p12); + p12 = NULL; + } else { + /* + * nothing to write out, so truncate the file + * (it will be deleted during close_keystore) + */ + if (!clear_keystore_file(err, keystore->capath)) { + pkgerr_add(err, PKGERR_WRITE, + gettext(ERR_KEYSTORE_WRITE), + keystore->capath); + ret = B_FALSE; + goto cleanup; + } + } + } + +cleanup: + if (p12 != NULL) + PKCS12_free(p12); + + return (ret); +} + +/* + * clear_keystore_file - Clears (zeros out) a keystore file. + * + * Arguments: + * err - Error object to add errors to + * dest - Path of keystore file to zero out. + * Returns: + * 0 - Success - Keystore file is truncated to zero length + * non-zero - Failure, errors and reasons recorded in err + */ +static boolean_t +clear_keystore_file(PKG_ERR *err, char *dest) +{ + int fd; + struct stat buf; + + fd = open(dest, O_RDWR|O_NONBLOCK); + if (fd == -1) { + /* can't open for writing */ + pkgerr_add(err, PKGERR_WRITE, gettext(MSG_OPEN), + errno); + return (B_FALSE); + } + + if ((fstat(fd, &buf) == -1) || !S_ISREG(buf.st_mode)) { + /* not a regular file */ + (void) close(fd); + pkgerr_add(err, PKGERR_WRITE, gettext(ERR_NOT_REG), + dest); + return (B_FALSE); + } + + if (ftruncate(fd, 0) == -1) { + (void) close(fd); + pkgerr_add(err, PKGERR_WRITE, gettext(ERR_WRITE), + dest, strerror(errno)); + return (B_FALSE); + } + + (void) close(fd); + return (B_TRUE); +} + +/* + * write_keystore_file - Writes keystore file to disk. + * + * Keystore files can possibly be corrupted by a variety + * of error conditions during reading/writing. This + * routine, along with restore_keystore_file, tries to + * maintain keystore integity by writing the files + * out in a particular order, minimizing the time period + * that the keystore is in an indeterminate state. + * + * With the current implementation, there are some failures + * that are wholly unrecoverable, such as disk corruption. + * These routines attempt to minimize the risk, but not + * eliminate it. When better, atomic operations are available + * (such as a true database with commit, rollback, and + * guaranteed atomicity), this implementation should use that. + * + * + * Arguments: + * err - Error object to add errors to + * dest - Destination filename + * contents - Contents to write to the file + * Returns: + * 0 - Success - Keystore contents are written out to + * the destination. + * non-zero - Failure, errors and reasons recorded in err + */ +static boolean_t +write_keystore_file(PKG_ERR *err, char *dest, PKCS12 *contents) +{ + FILE *newfile = NULL; + boolean_t ret = B_TRUE; + char newpath[MAXPATHLEN]; + char backuppath[MAXPATHLEN]; + struct stat buf; + int fd; + + (void) snprintf(newpath, MAXPATHLEN, "%s.new", dest); + (void) snprintf(backuppath, MAXPATHLEN, "%s.bak", dest); + + if ((fd = open(newpath, O_CREAT|O_EXCL|O_WRONLY|O_NONBLOCK, + S_IRUSR|S_IWUSR)) == -1) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN), + newpath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + + if (fstat(fd, &buf) == -1) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN), + newpath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + + if (!S_ISREG(buf.st_mode)) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_NOT_REG), + newpath); + ret = B_FALSE; + goto cleanup; + } + + if ((newfile = fdopen(fd, "w")) == NULL) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN), + newpath, strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + + if (i2d_PKCS12_fp(newfile, contents) == 0) { + pkgerr_add(err, PKGERR_WRITE, gettext(ERR_KEYSTORE_WRITE), + newpath); + ret = B_FALSE; + goto cleanup; + } + + /* flush, then close */ + (void) fflush(newfile); + (void) fclose(newfile); + newfile = NULL; + + /* now back up the original file */ + (void) rename(dest, backuppath); + + /* put new one in its place */ + (void) rename(newpath, dest); + + /* remove backup */ + (void) remove(backuppath); + +cleanup: + if (newfile != NULL) + (void) fclose(newfile); + if (fd != -1) + (void) close(fd); + + return (ret); +} + +/* + * read_keystore_file - Reads single keystore file + * off disk in PKCS12 format. + * + * Arguments: + * err - Error object to add errors to + * file - File path to read + * Returns: + * PKCS12 contents of file, or NULL if an error occurred. + * errors recorded in 'err'. + */ +static PKCS12 +*read_keystore_file(PKG_ERR *err, char *file) +{ + int fd; + struct stat buf; + FILE *newfile; + PKCS12 *p12 = NULL; + + if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN), + file, strerror(errno)); + goto cleanup; + } + + if (fstat(fd, &buf) == -1) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN), + file, strerror(errno)); + goto cleanup; + } + + if (!S_ISREG(buf.st_mode)) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_NOT_REG), + file); + goto cleanup; + } + + if ((newfile = fdopen(fd, "r")) == NULL) { + pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN), + file, strerror(errno)); + goto cleanup; + } + + if ((p12 = d2i_PKCS12_fp(newfile, NULL)) == NULL) { + pkgerr_add(err, PKGERR_CORRUPT, + gettext(ERR_KEYSTORE_CORRUPT), file); + goto cleanup; + } + +cleanup: + if (newfile != NULL) + (void) fclose(newfile); + if (fd != -1) + (void) close(fd); + + return (p12); +} + + +/* + * Locks the specified file. + */ +static int +file_lock(int fd, int type, int wait) +{ + struct flock lock; + + lock.l_type = type; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + if (!wait) { + if (file_lock_test(fd, type)) { + /* + * The caller would have to wait to get the + * lock on this file. + */ + return (-1); + } + } + + return (fcntl(fd, F_SETLKW, &lock)); +} + +/* + * Returns FALSE if the file is not locked; TRUE + * otherwise. + */ +static boolean_t +file_lock_test(int fd, int type) +{ + struct flock lock; + + lock.l_type = type; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + if (fcntl(fd, F_GETLK, &lock) != -1) { + if (lock.l_type != F_UNLCK) { + /* + * The caller would have to wait to get the + * lock on this file. + */ + return (B_TRUE); + } + } + + /* + * The file is not locked. + */ + return (B_FALSE); +} + +/* + * Unlocks the specified file. + */ +static int +file_unlock(int fd) +{ + struct flock lock; + + lock.l_type = F_UNLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + return (fcntl(fd, F_SETLK, &lock)); +} + +/* + * Determines if file has a length of 0 or not + */ +static boolean_t +file_empty(char *path) +{ + struct stat buf; + + /* file is empty if size = 0 or it doesn't exist */ + if (lstat(path, &buf) == 0) { + if (buf.st_size == 0) { + return (B_TRUE); + } + } else { + if (errno == ENOENT) { + return (B_TRUE); + } + } + + return (B_FALSE); +} + +/* + * Name: get_time_string + * Description: Generates a human-readable string from an ASN1_TIME + * + * Arguments: intime - The time to convert + * + * Returns : A pointer to a static string representing the passed-in time. + */ +static char +*get_time_string(ASN1_TIME *intime) +{ + + static char time[ATTR_MAX]; + BIO *mem; + char *p; + + if (intime == NULL) { + return (NULL); + } + if ((mem = BIO_new(BIO_s_mem())) == NULL) { + return (NULL); + } + + if (ASN1_TIME_print(mem, intime) == 0) { + (void) BIO_free(mem); + return (NULL); + } + + if (BIO_gets(mem, time, ATTR_MAX) <= 0) { + (void) BIO_free(mem); + return (NULL); + } + + (void) BIO_free(mem); + + /* trim the end of the string */ + for (p = time + strlen(time) - 1; isspace(*p); p--) { + *p = '\0'; + } + + return (time); +} + +/* + * check_password - do various password checks to see if the current password + * will work or we need to prompt for a new one. + * + * Arguments: + * pass - password to check + * + * Returns: + * B_TRUE - Password is OK. + * B_FALSE - Password not valid. + */ +static boolean_t +check_password(PKCS12 *p12, char *pass) +{ + boolean_t ret = B_TRUE; + + /* + * If password is zero length or NULL then try verifying both cases + * to determine which password is correct. The reason for this is that + * under PKCS#12 password based encryption no password and a zero + * length password are two different things... + */ + + /* Check the mac */ + if (pass == NULL || *pass == '\0') { + if (PKCS12_verify_mac(p12, NULL, 0) == 0 && + PKCS12_verify_mac(p12, "", 0) == 0) + ret = B_FALSE; + } else if (PKCS12_verify_mac(p12, pass, -1) == 0) { + ret = B_FALSE; + } + return (ret); +} diff --git a/usr/src/lib/libpkg/common/keystore.h b/usr/src/lib/libpkg/common/keystore.h new file mode 100644 index 0000000000..b48ba030aa --- /dev/null +++ b/usr/src/lib/libpkg/common/keystore.h @@ -0,0 +1,145 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _KEYSTORE_H +#define _KEYSTORE_H + + +/* + * Module: keystore.h + * Description: This module contains the structure definitions for processing + * package keystore files. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <openssl/evp.h> +#include <openssl/x509.h> +#include "pkgerr.h" + +/* keystore structures */ + +/* this opaque type represents a keystore */ +typedef void *keystore_handle_t; + +/* flags passed to open_keystore */ + +/* opens keystore read-only. Attempts to modify results in an error */ +#define KEYSTORE_ACCESS_READONLY 0x00000001L + +/* opens keystore read-write */ +#define KEYSTORE_ACCESS_READWRITE 0x00000002L + +/* + * tells open_keystore to fall back to app-generic paths in the case that + * the app-specific paths do not exist. + */ +#define KEYSTORE_PATH_SOFT 0x00000010L + +/* + * tells open_keystore to use the app-specific paths no matter what, + * failing if they cannot be used for any reason. + */ +#define KEYSTORE_PATH_HARD 0x00000020L + +/* masks off various types of flags */ +#define KEYSTORE_ACCESS_MASK 0x0000000FL +#define KEYSTORE_PATH_MASK 0x000000F0L + +/* default is read-only, soft */ +#define KEYSTORE_DFLT_FLAGS \ + (KEYSTORE_ACCESS_READONLY|KEYSTORE_PATH_SOFT) + +/* + * possible encoding formats used by the library, used + * by print_cert + */ +typedef enum { + KEYSTORE_FORMAT_PEM, + KEYSTORE_FORMAT_DER, + KEYSTORE_FORMAT_TEXT +} keystore_encoding_format_t; + +/* + * structure passed back to password callback for determining how + * to prompt for passphrase, and where to record errors + */ +typedef struct { + PKG_ERR *err; +} keystore_passphrase_data; + + +/* max length of a passphrase. One could use a short story! */ +#define KEYSTORE_PASS_MAX 1024 + +/* callback for collecting passphrase when open_keystore() is called */ +typedef int keystore_passphrase_cb(char *, int, int, void *); + +/* names of the individual files within the keystore path */ +#define TRUSTSTORE "truststore" +#define KEYSTORE "keystore" +#define CERTSTORE "certstore" + +/* keystore.c */ +extern int open_keystore(PKG_ERR *, char *, char *, + keystore_passphrase_cb, long flags, keystore_handle_t *); + +extern int print_certs(PKG_ERR *, keystore_handle_t, char *, + keystore_encoding_format_t, FILE *); + +extern int check_cert(PKG_ERR *, X509 *); + +extern int check_cert_and_key(PKG_ERR *, X509 *, EVP_PKEY *); + +extern int print_cert(PKG_ERR *, X509 *, + keystore_encoding_format_t, char *, boolean_t, FILE *); + +extern int close_keystore(PKG_ERR *, keystore_handle_t, + keystore_passphrase_cb); + +extern int merge_ca_cert(PKG_ERR *, X509 *, keystore_handle_t); +extern int merge_cert_and_key(PKG_ERR *, X509 *, EVP_PKEY *, + char *, keystore_handle_t); + +extern int delete_cert_and_keys(PKG_ERR *, keystore_handle_t, + char *); + +extern int find_key_cert_pair(PKG_ERR *, keystore_handle_t, + char *, EVP_PKEY **, X509 **); + +extern int find_ca_certs(PKG_ERR *, keystore_handle_t, + STACK_OF(X509) **); + +extern int find_cl_certs(PKG_ERR *, keystore_handle_t, + STACK_OF(X509) **); + +#ifdef __cplusplus +} +#endif + +#endif /* _KEYSTORE_H */ diff --git a/usr/src/lib/libpkg/common/llib-lpkg b/usr/src/lib/libpkg/common/llib-lpkg new file mode 100644 index 0000000000..b60597a9bc --- /dev/null +++ b/usr/src/lib/libpkg/common/llib-lpkg @@ -0,0 +1,36 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +/* LINTLIBRARY */ +/* PROTOLIB1 */ +#include <cfext.h> +#include <keystore.h> +#include <p12lib.h> +#include <pkgerr.h> +#include <pkglib.h> +#include <pkglocale.h> +#include <pkgweb.h> diff --git a/usr/src/lib/libpkg/common/logerr.c b/usr/src/lib/libpkg/common/logerr.c new file mode 100644 index 0000000000..7d304b059e --- /dev/null +++ b/usr/src/lib/libpkg/common/logerr.c @@ -0,0 +1,72 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include "pkglocale.h" + +/*VARARGS*/ +void +logerr(char *fmt, ...) +{ + va_list ap; + char *pt, buffer[2048]; + int flag; + char *estr = pkg_gt("ERROR:"); + char *wstr = pkg_gt("WARNING:"); + char *nstr = pkg_gt("NOTE:"); + + va_start(ap, fmt); + flag = 0; + /* This may have to use the i18n strcmp() routines. */ + if (strncmp(fmt, estr, strlen(estr)) && + strncmp(fmt, wstr, strlen(wstr)) && + strncmp(fmt, nstr, strlen(nstr))) { + flag++; + (void) fprintf(stderr, " "); + } + /* + * NOTE: internationalization in next line REQUIRES that caller of + * this routine be in the same internationalization domain + * as this library. + */ + (void) vsprintf(buffer, fmt, ap); + + va_end(ap); + + for (pt = buffer; *pt; pt++) { + (void) putc(*pt, stderr); + if (flag && (*pt == '\n') && pt[1]) + (void) fprintf(stderr, " "); + } + (void) putc('\n', stderr); +} diff --git a/usr/src/lib/libpkg/common/mapfile-vers b/usr/src/lib/libpkg/common/mapfile-vers new file mode 100644 index 0000000000..7cb5ad56f3 --- /dev/null +++ b/usr/src/lib/libpkg/common/mapfile-vers @@ -0,0 +1,223 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# MAPFILE HEADER START +# +# WARNING: STOP NOW. DO NOT MODIFY THIS FILE. +# Object versioning must comply with the rules detailed in +# +# usr/src/lib/README.mapfiles +# +# You should not be making modifications here until you've read the most current +# copy of that file. If you need help, contact a gatekeeper for guidance. +# +# MAPFILE HEADER END +# + +SUNWprivate { + global: + add_cache; + attrdefault; + attrpreset; + averify; + backoff; + basepath; + canonize; + canonize_slashes; + cgrgid; + cgrnam; + check_cert; + check_cert_and_key; + checksum_off; + checksum_on; + ckparam; + ckvolseq; + clgrgid; + clgrnam; + close_keystore; + clpwnam; + clpwuid; + compute_checksum; + cpwnam; + cpwuid; + cverify; + delete_cert_and_keys; + devtype; + disable_attribute_check; + ds_close; + ds_fd_open; + ds_findpkg; + ds_getinfo; + ds_getpkg; + ds_ginit; + ds_init; + ds_next; + ds_order; + ds_putinfo; + ds_readbuf; + ds_skiptoend; + ds_validate_signature; + e_ExecCmdArray; + e_ExecCmdList; + echo_out; + ecleanup; + enable_local_fs; + epclose; + epopen; + esystem; + find_ca_certs; + find_cl_certs; + find_key_cert_pair; + fmkdir; + fverify; + getErrbufAddr; + getErrbufSize; + getErrstr; + get_categories; + get_cert_chain; + get_disable_attribute_check; + get_endof_string; + get_prog_name; + get_proxy_port; + get_signature; + get_startof_string; + get_subject_display_name; + getmapmode; + gpkglist; + gpkgmap; + gpkgmapvfp; + holdcinfo; + init_cache; + isFdRemote; + isFstypeRemote; + isPathRemote; + is_not_valid_category; + is_not_valid_length; + is_web_install; + iscpio; + isdir; + isfile; + logerr; + lookup_cache; + mappath; + mapvar; + merge_ca_cert; + merge_cert_and_key; + nonABI_symlinks; + open_keystore; + path_valid; + pkg_passphrase_cb; + pkgalias; + pkgerr; + pkgerr_add; + pkgerr_clear; + pkgerr_dump; + pkgerr_free; + pkgerr_get; + pkgerr_new; + pkgerr_num; + pkgexecl; + pkgexecv; + pkghead; + pkglist_cont; + pkgmount; + pkgstrAddToken; + pkgstrContainsToken; + pkgstrConvertPathToBasename; + pkgstrConvertPathToDirname; + pkgstrConvertUllToTimeString_r; + pkgstrExpandTokens; + pkgstrGetToken; + pkgstrGetToken_r; + pkgstrLocatePathBasename; + pkgstrNumTokens; + pkgstrPrintf; + pkgstrPrintf_r; + pkgstrRemoveLeadingWhitespace; + pkgstrRemoveToken; + pkgstrScaleNumericString; + pkgtrans; + pkgumount; + ppkgmap; + print_cert; + print_certs; + progerr; + putcfile; + putcvfpfile; + reset_backoff; + restore_local_fs; + rpterr; + rrmdir; + sec_init; + setErrstr; + set_memalloc_failure_func; + set_nonABI_symlinks; + set_passphrase_passarg; + set_passphrase_prompt; + set_prog_name; + set_web_install; + setmapmode; + srchcfile; + strip_port; + sunw_PKCS12_contents; + sunw_PKCS12_create; + sunw_check_cert_times; + sunw_check_keys; + sunw_evp_pkey_free; + sunw_find_fname; + sunw_find_localkeyid; + sunw_get_cert_fname; + sunw_get_pkey_fname; + sunw_get_pkey_localkeyid; + sunw_set_fname; + sunw_set_localkeyid; + sunw_split_certs; + sunw_PEM_contents; + tputcfent; + validate_signature; + vfpCheckpointFile; + vfpCheckpointOpen; + vfpClearModified; + vfpClose; + vfpGetModified; + vfpOpen; + vfpRewind; + vfpSafePwrite; + vfpSafeWrite; + vfpSetFlags; + vfpSetModified; + vfpSetSize; + vfpTruncate; + vfpWriteToFile; + web_cleanup; + web_session_control; + xmalloc; + xrealloc; + xstrdup; + local: + *; +}; diff --git a/usr/src/lib/libpkg/common/mappath.c b/usr/src/lib/libpkg/common/mappath.c new file mode 100644 index 0000000000..b0ae99d827 --- /dev/null +++ b/usr/src/lib/libpkg/common/mappath.c @@ -0,0 +1,236 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> + +/* 0 = both upper and lower case */ +/* 1 = initial lower case only (build variables) */ +/* 2 = initial upper case only (install variables) */ +#define mode(flag, pt) (!flag || ((flag == 1) && islower(pt[1])) || \ + ((flag == 2) && isupper(pt[1]))) + +/* + * For next and last functions below, values indicate whether resolution + * was possible. + * + * 0 = all OK - the variable resolved within the established parameters + * or it wasn't time for the variable to bind. + * 1 = parameter did not resolve because there was no value in the + * environment or because it was a build variable at install + * time. + */ + +/* + * This gets a raw path which may contain shell variables and returns in path + * a pathname with all appropriate parameters resolved. If it comes in + * relative, it goes out relative. + */ +int +mappath(int flag, char *path) +{ + char buffer[PATH_MAX]; + char varname[64]; + char *npt, *pt, *pt2, *copy; + char *token; + int retvalue = 0; + + copy = buffer; + + /* + * For each "/" separated token. If the token contains an environment + * variable, then evaluate the variable and insert it into path. + */ + for (pt = path; *pt; /* void */) { + /* + * If this is a token and it's an environment variable + * properly situated in the path... + */ + if ((*pt == '$') && isalpha(pt[1]) && + ((pt == path) || (pt[-1] == '/'))) { + /* ... and it's the right time to evaluate it... */ + if (mode(flag, pt)) { + /* replace the parameter with its value. */ + pt2 = varname; + for (npt = pt+1; *npt && (*npt != '/'); + /* void */) + *pt2++ = *npt++; + *pt2 = '\0'; + /* + * At this point EVERY token should evaluate + * to a value. If it doesn't, there's an + * error. + */ + if ((token = getenv(varname)) != NULL && + *token != NULL) { + /* copy in parameter value */ + while (*token) + *copy++ = *token++; + pt = npt; + } else { + retvalue = 1; + *copy++ = *pt++; + } + /* + * If evaluate time is wrong, determine of this is an + * error. + */ + } else { + if (flag == 2) { /* install-time. */ + /* + * ALL variables MUST evaluate at + * install time. + */ + *copy++ = *pt++; + retvalue = 1; + } else if (flag == 1 && /* build-time */ + islower(pt[1])) { + /* + * All build-time variables must + * evaluate at build time. + */ + retvalue = 1; + *copy++ = *pt++; + } else /* no problem. */ + *copy++ = *pt++; + } + /* + * If it's a separator, copy it over to the target buffer and + * move to the start of the next token. + */ + } else if (*pt == '/') { + while (pt[1] == '/') + pt++; + if ((pt[1] == '\0') && (pt > path)) + break; + *copy++ = *pt++; + /* + * If we're in the middle of a non-parametric token, copy + * that character over and try the next character. + */ + } else + *copy++ = *pt++; + } + *copy = '\0'; + (void) strcpy(path, buffer); + return (retvalue); +} + +/* + * This function resolves the path into an absolute path referred to + * an install root of ir. + */ +void +basepath(char *path, char *basedir, char *ir) +{ + char buffer[PATH_MAX]; + + /* For a relative path, prepend the basedir */ + if (*path != '/') { + (void) strcpy(buffer, path); + if (ir && *ir) { + while (*ir) + *path++ = *ir++; + if (path[-1] == '/') + path--; + } + if (basedir && *basedir) { + if (ir && *ir && *basedir != '/') + *path++ = '/'; + while (*basedir) + *path++ = *basedir++; + if (path[-1] == '/') + path--; + } + *path++ = '/'; + (void) strcpy(path, buffer); + + /* For an absolute path, just prepend the install root */ + } else { + if (ir && *ir) { + (void) strcpy(buffer, path); + while (*ir) + *path++ = *ir++; + if (path[-1] == '/') + path--; + (void) strcpy(path, buffer); + } + } +} + +/* + * Evaluate varname and return with environment variables resolved. + * NOTE: This assumes that varname is a buffer long enough to hold the + * evaluated string. + */ +int +mapvar(int flag, char *varname) +{ + char *token; + int retvalue = 0; + + /* If its a parametric entry beginning with an alpha character. */ + if (*varname == '$' && isalpha(varname[1])) { + /* ...and it's the right time to evaluate it... */ + if (mode(flag, varname)) { + /* + * then it MUST be possible to evaluate it. If not, + * there's an error. + */ + if (((token = getenv(&varname[1])) != NULL) && + *token) { + /* copy token into varname */ + while (*token) + *varname++ = *token++; + *varname = '\0'; + } else + retvalue = 1; + } else { + if (flag == 2) /* install-time. */ + /* + * ALL variables MUST evaluate at install + * time. + */ + retvalue = 1; + else if (flag == 1 && /* build-time */ + islower(varname[1])) + /* + * all build-time variables must evaluate at + * build time. + */ + retvalue = 1; + } + } + return (retvalue); +} diff --git a/usr/src/lib/libpkg/common/ncgrpw.c b/usr/src/lib/libpkg/common/ncgrpw.c new file mode 100644 index 0000000000..cf3151164e --- /dev/null +++ b/usr/src/lib/libpkg/common/ncgrpw.c @@ -0,0 +1,740 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +/* + * This module fetches group and passwd structs for the caller. It + * uses a hash table to speed up retrieval of repeated entries. If + * the attempts to initialize the hash tables fail, this just + * continues the slow way. + */ + +#include <pwd.h> +#include <grp.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "pkglib.h" +#include "pkglocale.h" +#include "nhash.h" + +#define HASHSIZE 151 +#define BSZ 4 + +#define ERR_DUPFAIL "%s: strdup(%s) failed.\n" +#define ERR_ADDFAIL "%s: add_cache() failed.\n" +#define ERR_BADMEMB "%s: %s in \"%s\" %s structure is invalid.\n" +#define ERR_NOGRP "dup_gr_ent(): no group entry provided.\n" +#define ERR_NOPWD "dup_pw_ent(): no passwd entry provided.\n" +#define ERR_NOINIT "%s: init_cache() failed.\n" +#define ERR_MALLOC "%s: malloc(%d) failed for %s.\n" + +static Cache *pwnam_cache = (Cache *) NULL; +static Cache *grnam_cache = (Cache *) NULL; +static Cache *pwuid_cache = (Cache *) NULL; +static Cache *grgid_cache = (Cache *) NULL; + +static int dup_gr_ent(struct group *grp); +static int dup_pw_ent(struct passwd *pwp); + +/* + * These indicate whether the hash table has been initialized for the four + * categories. + */ +static int is_a_pwnam_cache; +static int is_a_grnam_cache; +static int is_a_pwuid_cache; +static int is_a_grgid_cache; + +extern char *get_install_root(void); + +/* + * If there's a grnam cache, then update it with this new + * group, otherwise, skip it. + */ +static Item * +cache_alloc(char *fname, int len, size_t struct_size) +{ + Item *itemp; + + /* + * Allocate space for the Item pointer, key and data. + */ + if ((itemp = (Item *) malloc(sizeof (*itemp))) == + Null_Item) { + (void) fprintf(stderr, + pkg_gt(ERR_MALLOC), fname, + sizeof (*itemp), "itemp"); + } else if ((itemp->key = (char *)malloc(len)) == NULL) { + (void) fprintf(stderr, pkg_gt(ERR_MALLOC), fname, len, + "itemp->key"); + free(itemp); + } else if ((itemp->data = malloc(struct_size)) == NULL) { + (void) fprintf(stderr, pkg_gt(ERR_MALLOC), fname, + struct_size, "itemp->data"); + free(itemp->key); + free(itemp); + } else { + /* Set length parameters. */ + itemp->keyl = len; + itemp->datal = struct_size; + + return (itemp); + } + + return ((Item *) NULL); +} + +/* Get the required group structure based upon the group name. */ +struct group * +cgrnam(char *nam) +{ + struct group *grp; + Item *itemp; + int len; + static int cache_failed; + + /* Attempt to initialize the grname cache. */ + if (!is_a_grnam_cache && !cache_failed) { + if (init_cache(&grnam_cache, HASHSIZE, BSZ, + (int (*)())NULL, (int (*)())NULL) == -1) { + (void) fprintf(stderr, pkg_gt(ERR_NOINIT), "cgrnam()"); + grnam_cache = (Cache *) NULL; + cache_failed = 1; + } else + is_a_grnam_cache = 1; + } + + len = strlen(nam) + 1; + + /* First look in the cache. Failing that, do it the hard way. */ + if ((itemp = lookup_cache(grnam_cache, nam, len)) == Null_Item) { + + /* Get the group by name. */ + if ((grp = clgrnam(nam)) != NULL || + (grp = getgrnam(nam)) != NULL) { + /* A group by that name exists on this machine. */ + if (dup_gr_ent(grp)) + /* + * Effectively no such group since struct + * couldn't be duplicated. + */ + grp = (struct group *)NULL; + /* + * If there's a grnam cache, then update it with this + * new group, otherwise, skip it. + */ + else if (is_a_grnam_cache) { + if ((itemp = cache_alloc("cgrnam()", len, + sizeof (struct group))) != Null_Item) { + /* + * With that allocated, insert the + * group name as key and set the key + * length. + */ + (void) memmove(itemp->key, nam, len); + + /* + * Insert the data associated with + * the key and the data length. + */ + (void) memmove(itemp->data, grp, + sizeof (struct group)); + + /* Insert the Item into the cache. */ + if (add_cache(grnam_cache, itemp) == -1) + (void) fprintf(stderr, + pkg_gt(ERR_ADDFAIL), + "cgrnam()"); + } + } + } + return (grp); + } else /* Found it in the cache. */ + return ((struct group *)itemp->data); +} + +struct passwd * +cpwnam(char *nam) +{ + struct passwd *pwd; + Item *itemp; + int len; + static int cache_failed; + + if (!is_a_pwnam_cache && !cache_failed) { + if (init_cache(&pwnam_cache, HASHSIZE, BSZ, + (int (*)())NULL, (int (*)())NULL) == -1) { + (void) fprintf(stderr, pkg_gt(ERR_NOINIT), "cpwnam()"); + pwnam_cache = (Cache *) NULL; + cache_failed = 1; + } else + is_a_pwnam_cache = 1; + } + + len = strlen(nam) + 1; + + /* First look in the cache. Failing that, do it the hard way. */ + if ((itemp = lookup_cache(pwnam_cache, nam, len)) == Null_Item) { + + /* Get the passwd by name. */ + if ((pwd = clpwnam(nam)) != NULL || + (pwd = getpwnam(nam)) != NULL) { + /* A passwd by that name exists on this machine. */ + if (dup_pw_ent(pwd)) + /* + * Effectively no such passwd since struct + * couldn't be duplicated. + */ + pwd = (struct passwd *)NULL; + /* + * If there's a pwnam cache, then update it with this + * new passwd, otherwise, skip it. + */ + else if (is_a_pwnam_cache) { + /* + * Allocate space for the Item pointer, key + * and data. + */ + if ((itemp = cache_alloc("cpwnam()", len, + sizeof (struct passwd))) != Null_Item) { + /* + * With that allocated, insert the + * group name as key and set the key + * length. + */ + (void) memmove(itemp->key, nam, len); + + /* + * Insert the data associated with + * the key and the data length. + */ + (void) memmove(itemp->data, pwd, + sizeof (struct passwd)); + + if (add_cache(pwnam_cache, itemp) == -1) + (void) fprintf(stderr, + pkg_gt(ERR_ADDFAIL), + "cpwnam()"); + } + } + } + return (pwd); + } else /* Found it in the cache. */ + return ((struct passwd *)itemp->data); +} + +static int +uid_hash(void *datap, int datalen, int hsz) +{ +#ifdef lint + int i = datalen; + datalen = i; +#endif /* lint */ + + return (*((uid_t *)datap) % hsz); +} + +static int +uid_comp(void *datap1, void *datap2, int datalen) +{ +#ifdef lint + int i = datalen; + datalen = i; +#endif /* lint */ + + return (*((uid_t *)datap1) - *((uid_t *)datap2)); +} + +struct group * +cgrgid(gid_t gid) +{ + struct group *grp; + Item *itemp; + int len; + static int cache_failed; + + if (!is_a_grgid_cache && !cache_failed) { + if (init_cache(&grgid_cache, HASHSIZE, BSZ, + uid_hash, uid_comp) == -1) { + (void) fprintf(stderr, pkg_gt(ERR_NOINIT), "cgrgid()"); + grgid_cache = (Cache *) NULL; + cache_failed = 1; + } else + is_a_grgid_cache = 1; + } + + len = sizeof (uid_t); + + /* First look in the cache. Failing that, do it the hard way. */ + if ((itemp = lookup_cache(grgid_cache, &gid, len)) == Null_Item) { + if ((grp = clgrgid(gid)) != NULL || + (grp = getgrgid(gid)) != NULL) { + /* A group by that number exists on this machine. */ + if (dup_gr_ent(grp)) + /* + * Effectively no such group since struct + * couldn't be duplicated. + */ + grp = (struct group *)NULL; + /* + * If there's a grnam cache, then update it with this + * new group, otherwise, skip it. + */ + else if (is_a_grgid_cache) { + if ((itemp = cache_alloc("cgrgid()", len, + sizeof (struct group))) != Null_Item) { + /* + * With that allocated, insert the + * group name as key and set the key + * length. + */ + (void) memmove(itemp->key, &gid, len); + + /* + * Insert the data associated with + * the key and the data length. + */ + (void) memmove(itemp->data, grp, + sizeof (struct group)); + + if (add_cache(grgid_cache, itemp) == -1) + (void) fprintf(stderr, + pkg_gt(ERR_ADDFAIL), + "cgrgid()"); + } + } + } + return (grp); + } else /* Found it in the cache. */ + return ((struct group *)itemp->data); +} + +struct passwd * +cpwuid(uid_t uid) +{ + struct passwd *pwd; + Item *itemp; + int len; + static int cache_failed; + + if (!is_a_pwuid_cache && !cache_failed) { + if (init_cache(&pwuid_cache, HASHSIZE, BSZ, + uid_hash, uid_comp) == -1) { + (void) fprintf(stderr, + pkg_gt(ERR_NOINIT), "cpwuid()"); + pwuid_cache = (Cache *) NULL; + cache_failed = 1; + } else + is_a_pwuid_cache = 1; + } + + len = sizeof (uid_t); + + /* First look in the cache. Failing that, do it the hard way. */ + if ((itemp = lookup_cache(pwuid_cache, &uid, len)) == Null_Item) { + + /* Get the passwd by number. */ + if ((pwd = clpwuid(uid)) != NULL || + (pwd = getpwuid(uid)) != NULL) { + /* A passwd by that user ID exists on this machine. */ + if (dup_pw_ent(pwd)) + /* + * Effectively no such passwd since struct + * couldn't be duplicated. + */ + pwd = (struct passwd *)NULL; + /* + * If there's a pwuid cache, then update it with this + * new passwd, otherwise, skip it. + */ + else if (is_a_pwuid_cache) { + if ((itemp = cache_alloc("cpwuid()", len, + sizeof (struct passwd))) != Null_Item) { + /* + * With that allocated, insert the + * group name as key and set the key + * length. + */ + (void) memmove(itemp->key, &uid, len); + + /* + * Insert the data associated with + * the key and the data length. + */ + (void) memmove(itemp->data, pwd, + sizeof (struct passwd)); + + if (add_cache(pwuid_cache, itemp) == -1) + (void) fprintf(stderr, + pkg_gt(ERR_ADDFAIL), + "cpwuid()"); + } + } + } + return (pwd); + } else /* Found it in the cache. */ + return ((struct passwd *)itemp->data); +} + +/* + * This function duplicates the group structure provided from kernel static + * memory. There is a lot of defensive coding here because there have been + * problems with the getgr*() functions. They will sometimes provide NULL + * values instead of pointers to NULL values. There has been no explanation + * for the reason behind this; but, this function takes a NULL to be an + * invalid (char *) and returns an error. + */ +static int +dup_gr_ent(struct group *grp) +{ + char **tp = NULL; + char **memp = NULL; + int nent = 0; /* Number of entries in the member list. */ + + if (grp) { + if (grp->gr_name == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_gr_ent()", "gr_name", + "unknown", "group"); + return (-1); + } else if ((grp->gr_name = strdup(grp->gr_name)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_gr_ent()", "gr_name"); + return (-1); + } + if (grp->gr_passwd == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_gr_ent()", "gr_passwd", + grp->gr_name, "group"); + return (-1); + } else if ((grp->gr_passwd = strdup(grp->gr_passwd)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_gr_ent()", "gr_passwd"); + return (-1); + } + /* + * Allocate space for the member list and move the members + * into it. + */ + if (grp->gr_mem) { + /* + * First count the members. The nent variable will be + * the number of members + 1 for the terminator. + */ + for (tp = grp->gr_mem; *tp; nent++, tp++); + + /* Now allocate room for the pointers. */ + memp = malloc(sizeof (char **)* (nent+1)); + + if (memp == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_MALLOC), "dup_gr_ent()", + (sizeof (char **)* (nent+1)), + "memp"); + return (-1); + } + + /* + * Now copy over the pointers and entries. It should + * be noted that if the structure is messed up here, + * the resulting member list will be truncated at the + * NULL entry. + */ + for (nent = 0, tp = grp->gr_mem; *tp; tp++) { + if ((memp[nent++] = strdup(*tp)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_gr_ent()", + "gr_mem"); + return (-1); + } + } + } else { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_gr_ent()", "gr_mem", + grp->gr_name, "group"); + return (-1); + } + } else { + (void) fprintf(stderr, pkg_gt(ERR_NOGRP)); + return (-1); + } + memp[nent++] = '\0'; + return (0); +} + +/* + * This function duplicates the passwd structure provided from kernel static + * memory. As in the above function, since there have been problems with the + * getpw*() functions, the structure provided is rigorously scrubbed. This + * function takes a NULL to be an invalid (char *) and returns an error if + * one is detected. + */ +static int +dup_pw_ent(struct passwd *pwd) +{ + if (pwd) { + if (pwd->pw_name == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_name", + "unknown", "passwd"); + return (-1); + } else if ((pwd->pw_name = strdup(pwd->pw_name)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_name"); + return (-1); + } + + if (pwd->pw_passwd == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_passwd", + pwd->pw_name, "passwd"); + return (-1); + } else if ((pwd->pw_passwd = strdup(pwd->pw_passwd)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_passwd"); + return (-1); + } + + if (pwd->pw_age == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_age", + pwd->pw_name, "passwd"); + return (-1); + } else if ((pwd->pw_age = strdup(pwd->pw_age)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_age"); + return (-1); + } + + if (pwd->pw_comment == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_comment", + pwd->pw_name, "passwd"); + return (-1); + } else if ((pwd->pw_comment = strdup(pwd->pw_comment)) == + NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_comment"); + return (-1); + } + + if (pwd->pw_gecos == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_gecos", + pwd->pw_name, "passwd"); + return (-1); + } else if ((pwd->pw_gecos = strdup(pwd->pw_gecos)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_gecos"); + return (-1); + } + + if (pwd->pw_dir == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_dir", + pwd->pw_name, "passwd"); + return (-1); + } else if ((pwd->pw_dir = strdup(pwd->pw_dir)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_dir"); + return (-1); + } + + if (pwd->pw_shell == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_BADMEMB), "dup_pw_ent()", "pw_shell", + pwd->pw_name, "passwd"); + return (-1); + } else if ((pwd->pw_shell = strdup(pwd->pw_shell)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_DUPFAIL), "dup_pw_ent()", "pw_shell"); + return (-1); + } + } else { + (void) fprintf(stderr, pkg_gt(ERR_NOPWD)); + return (-1); + } + + return (0); +} + +/* + * Check the client's etc/group file for the group name + * + * returns a pointer to the group structure if the group is found + * returns NULL if not found + */ +struct group * +clgrnam(char *nam) +{ + struct group *gr; + char *instroot, *buf; + FILE *gr_ptr; + + if ((instroot = get_install_root()) != NULL) { + if ((buf = (char *)malloc(strlen(instroot) + + strlen(GROUP) + 1)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_MALLOC), "clgrnam()", + strlen(instroot) + strlen(GROUP), "buf"); + } + (void) sprintf(buf, "%s%s", instroot, GROUP); + if ((gr_ptr = fopen(buf, "r")) == NULL) { + free(buf); + return (NULL); + } else { + while ((gr = fgetgrent(gr_ptr)) != NULL) { + if (strcmp(gr->gr_name, nam) == 0) { + break; + } + } + } + free(buf); + (void) fclose(gr_ptr); + return (gr); + } else { + return (NULL); + } +} + +/* + * Check the client's etc/passwd file for the user name + * + * returns a pointer to the passwd structure if the passwd is found + * returns NULL if not found + */ +struct passwd * +clpwnam(char *nam) +{ + struct passwd *pw; + char *instroot, *buf; + FILE *pw_ptr; + + if ((instroot = get_install_root()) != NULL) { + if ((buf = (char *)malloc(strlen(instroot) + + strlen(PASSWD) + 1)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_MALLOC), "clpwnam()", + strlen(instroot) + strlen(PASSWD), "buf"); + } + (void) sprintf(buf, "%s%s", instroot, PASSWD); + if ((pw_ptr = fopen(buf, "r")) == NULL) { + free(buf); + return (NULL); + } else { + while ((pw = fgetpwent(pw_ptr)) != NULL) { + if (strcmp(pw->pw_name, nam) == 0) { + break; + } + } + } + free(buf); + (void) fclose(pw_ptr); + return (pw); + } else { + return (NULL); + } +} + +/* + * Check the client's etc/group file for the group id + * + * returns a pointer to the group structure if the group id is found + * returns NULL if not found + */ +struct group * +clgrgid(gid_t gid) +{ + struct group *gr; + char *instroot, *buf; + FILE *gr_ptr; + + if ((instroot = get_install_root()) != NULL) { + if ((buf = (char *)malloc(strlen(instroot) + + strlen(GROUP) + 1)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_MALLOC), "clgrgid()", + strlen(instroot) + strlen(GROUP), "buf"); + } + (void) sprintf(buf, "%s%s", instroot, GROUP); + if ((gr_ptr = fopen(buf, "r")) == NULL) { + free(buf); + return (NULL); + } else { + while ((gr = fgetgrent(gr_ptr)) != NULL) { + if (gr->gr_gid == gid) { + break; + } + } + } + free(buf); + (void) fclose(gr_ptr); + return (gr); + } else { + return (NULL); + } +} + +/* + * Check the client's etc/passwd file for the user id + * + * returns a pointer to the passwd structure if the user id is found + * returns NULL if not found + */ +struct passwd * +clpwuid(uid_t uid) +{ + struct passwd *pw; + char *instroot, *buf; + FILE *pw_ptr; + + if ((instroot = get_install_root()) != NULL) { + if ((buf = (char *)malloc(strlen(instroot) + + strlen(PASSWD) + 1)) == NULL) { + (void) fprintf(stderr, + pkg_gt(ERR_MALLOC), "clpwuid()", + strlen(instroot) + strlen(PASSWD), "buf"); + } + (void) sprintf(buf, "%s%s", instroot, PASSWD); + if ((pw_ptr = fopen(buf, "r")) == NULL) { + free(buf); + return (NULL); + } else { + while ((pw = fgetpwent(pw_ptr)) != NULL) { + if (pw->pw_uid == uid) { + break; + } + } + } + free(buf); + (void) fclose(pw_ptr); + return (pw); + } else { + return (NULL); + } +} diff --git a/usr/src/lib/libpkg/common/nhash.c b/usr/src/lib/libpkg/common/nhash.c new file mode 100644 index 0000000000..1b42595fec --- /dev/null +++ b/usr/src/lib/libpkg/common/nhash.c @@ -0,0 +1,182 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <strings.h> +#include "pkglib.h" +#include "nhash.h" +#include "pkglocale.h" + +#ifndef _KERNEL +#define bcopy(a, b, c) (void) memmove(b, a, c) +#define bcmp memcmp +#define bzero(a, c) (void) memset(a, '\0', c) +#else /* _KERNEL */ +#define malloc bkmem_alloc +#endif /* _KERNEL */ + +#define VERIFY_HASH_REALLOC + +static int +BCMP(void *str1, void *str2, int len) +{ + return (bcmp((char *)str1, (char *)str2, len)); +} + +static int +HASH(void *datap, int datalen, int hsz) +{ + char *cp; + char *np; + int hv = 0; + + /* determine starting and ending positions */ + + cp = (char *)datap; + np = ((char *)cp + datalen); + + /* compute hash over all characters from start to end */ + + while (cp != np) { + hv += ((int)*cp++); + } + + /* return computed hash */ + + return (hv % hsz); +} + +int +init_cache(Cache **cp, int hsz, int bsz, + int (*hfunc)(void *, int, int), int (*cfunc)(void *, void *, int)) +{ + if ((*cp = (Cache *) malloc(sizeof (**cp))) == NULL) { + (void) fprintf(stderr, pkg_gt("malloc(Cache **cp)")); + return (-1); + } + if (((*cp)->bp = + (Bucket *) malloc(sizeof (*(*cp)->bp) * hsz)) == NULL) { + (void) fprintf(stderr, pkg_gt("malloc(Bucket cp->bp)")); + return (-1); + } + + (*cp)->hsz = hsz; + (*cp)->bsz = bsz; + + bzero((*cp)->bp, sizeof (*(*cp)->bp) * hsz); + + if (hfunc != (int (*)()) NULL) { + (*cp)->hfunc = hfunc; + } else { + (*cp)->hfunc = HASH; + } + + if (cfunc != (int (*)()) NULL) { + (*cp)->cfunc = cfunc; + } else { + (*cp)->cfunc = BCMP; + } + return (0); +} + +int +add_cache(Cache *cp, Item *itemp) +{ + Bucket *bp; + Item **titempp; + + /* + * If cp is NULL, then init_cache() wasn't called. Quietly return the + * error code and let the caller deal with it. + */ + if (cp == NULL) + return (-1); + + bp = &cp->bp[(*cp->hfunc)(itemp->key, itemp->keyl, cp->hsz)]; + if (bp->nent >= bp->nalloc) { + if (bp->nalloc == 0) { + bp->itempp = + (Item **) malloc(sizeof (*bp->itempp) * cp->bsz); + } else { +#ifdef VERIFY_HASH_REALLOC + (void) fprintf(stderr, + pkg_gt("realloc(%d) bucket=%d\n"), + bp->nalloc + cp->bsz, + (*cp->hfunc)(itemp->key, itemp->keyl, cp->hsz)); +#endif /* VERIFY_HASH_REALLOC */ + if ((titempp = + (Item **) malloc(sizeof (*bp->itempp) * + (bp->nalloc + cp->bsz))) != NULL) { + bcopy((char *)bp->itempp, (char *)titempp, + (sizeof (*bp->itempp) * bp->nalloc)); +#ifdef _KERNEL + bkmem_free(bp->itempp, + (sizeof (*bp->itempp) * bp->nalloc)); +#else /* !_KERNEL */ + free(bp->itempp); +#endif /* _KERNEL */ + bp->itempp = titempp; + } else + bp->itempp = NULL; + } + if (bp->itempp == NULL) { + (void) fprintf(stderr, + pkg_gt("add_cache(): out of memory\n")); + return (-1); + } + bp->nalloc += cp->bsz; + } + bp->itempp[bp->nent] = itemp; + bp->nent++; + return (0); +} + +Item * +lookup_cache(Cache *cp, void *datap, int datalen) +{ + int i; + Bucket *bp; + + /* + * If cp is NULL, then init_cache() wasn't called. Quietly return the + * error code and let the caller deal with it. + */ + if (cp == NULL) { + return (Null_Item); + } + + bp = &cp->bp[(*cp->hfunc)(datap, datalen, cp->hsz)]; + + for (i = 0; i < bp->nent; i++) { + if (!(*cp->cfunc)((void *)bp->itempp[i]->key, datap, datalen)) { + return (bp->itempp[i]); + } + } + return (Null_Item); +} diff --git a/usr/src/lib/libpkg/common/nhash.h b/usr/src/lib/libpkg/common/nhash.h new file mode 100644 index 0000000000..cc32228cb9 --- /dev/null +++ b/usr/src/lib/libpkg/common/nhash.h @@ -0,0 +1,75 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _NHASH_H +#define _NHASH_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NULL +#define NULL 0 +#endif /* NULL */ + +typedef struct item_t { + void *key; + int keyl; + void *data; + int datal; +} Item; + +#define Null_Item ((Item *) NULL) + +typedef struct bucket_t { + int nent; + int nalloc; + Item **itempp; +} Bucket; + +typedef struct cache_t { + int hsz; + int bsz; + Bucket *bp; + int (*hfunc)(void *, int, int); + int (*cfunc)(void *, void *, int); +} Cache; + +#ifdef _KERNEL +#define malloc bkmem_alloc +#endif /* _KERNEL */ + +extern int init_cache(Cache **cp, int hsz, int bsz, + int (*hfunc)(void *, int, int), int (*cfunc)(void *, void *, int)); +extern int add_cache(Cache *cp, Item *itemp); +extern Item *lookup_cache(Cache *cp, void *datap, int datalen); + +#ifdef __cplusplus +} +#endif + +#endif /* _NHASH_H */ diff --git a/usr/src/lib/libpkg/common/p12lib.c b/usr/src/lib/libpkg/common/p12lib.c new file mode 100644 index 0000000000..3ae72a4b50 --- /dev/null +++ b/usr/src/lib/libpkg/common/p12lib.c @@ -0,0 +1,2786 @@ +/* + * ==================================================================== + * Copyright (c) 1999 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#include <strings.h> +#include <stdlib.h> +#include <assert.h> + +#include <openssl/crypto.h> +#include <openssl/err.h> +#include <openssl/x509.h> +#include <openssl/pem.h> + +#include <openssl/pkcs12.h> +#include "p12lib.h" + +/* + * OpenSSL provides a framework for pushing error codes onto a stack. + * When an error occurs, the consumer may use the framework to + * pop the errors off the stack and provide a trace of where the + * errors occurred. + * + * Our PKCS12 code plugs into this framework by calling + * ERR_load_SUNW_strings(). To push an error (which by the way, consists + * of a function code and an error code) onto the stack our PKCS12 code + * calls SUNWerr(). + * + * Consumers of our PKCS12 code can then call the OpenSSL error routines + * when an error occurs and retrieve the stack of errors. + */ + +#ifndef OPENSSL_NO_ERR + +/* Function codes and their matching strings */ +static ERR_STRING_DATA SUNW_str_functs[] = { + { ERR_PACK(0, SUNW_F_USE_X509CERT, 0), "sunw_use_x509cert" }, + { ERR_PACK(0, SUNW_F_USE_PKEY, 0), "sunw_use_pkey" }, + { ERR_PACK(0, SUNW_F_USE_TASTORE, 0), "sunw_use_tastore" }, + { ERR_PACK(0, SUNW_F_USE_CERTFILE, 0), "sunw_p12_use_certfile" }, + { ERR_PACK(0, SUNW_F_USE_KEYFILE, 0), "sunw_p12_use_keyfile" }, + { ERR_PACK(0, SUNW_F_USE_TRUSTFILE, 0), "sunw_p12_use_trustfile" }, + { ERR_PACK(0, SUNW_F_READ_FILE, 0), "p12_read_file" }, + { ERR_PACK(0, SUNW_F_DOPARSE, 0), "p12_doparse" }, + { ERR_PACK(0, SUNW_F_PKCS12_PARSE, 0), "sunw_PKCS12_parse" }, + { ERR_PACK(0, SUNW_F_PKCS12_CONTENTS, 0), "sunw_PKCS12_contents" }, + { ERR_PACK(0, SUNW_F_PARSE_ONE_BAG, 0), "parse_one_bag" }, + { ERR_PACK(0, SUNW_F_PKCS12_CREATE, 0), "sunw_PKCS12_create" }, + { ERR_PACK(0, SUNW_F_SPLIT_CERTS, 0), "sunw_split_certs" }, + { ERR_PACK(0, SUNW_F_FIND_LOCALKEYID, 0), "sunw_find_localkeyid" }, + { ERR_PACK(0, SUNW_F_SET_LOCALKEYID, 0), "sunw_set_localkeyid" }, + { ERR_PACK(0, SUNW_F_GET_LOCALKEYID, 0), "sunw_get_localkeyid" }, + { ERR_PACK(0, SUNW_F_SET_FNAME, 0), "sunw_set_fname" }, + { ERR_PACK(0, SUNW_F_GET_PKEY_FNAME, 0), "sunw_get_pkey_fname" }, + { ERR_PACK(0, SUNW_F_APPEND_KEYS, 0), "sunw_append_keys" }, + { ERR_PACK(0, SUNW_F_PEM_CONTENTS, 0), "sunw_PEM_contents" }, + { ERR_PACK(0, SUNW_F_PEM_INFO, 0), "pem_info" }, + { ERR_PACK(0, SUNW_F_ASC2BMPSTRING, 0), "asc2bmpstring" }, + { ERR_PACK(0, SUNW_F_UTF82ASCSTR, 0), "utf82ascstr" }, + { ERR_PACK(0, SUNW_F_FINDATTR, 0), "findattr" }, + { ERR_PACK(0, SUNW_F_TYPE2ATTRIB, 0), "type2attrib" }, + { ERR_PACK(0, SUNW_F_MOVE_CERTS, 0), "move_certs" }, + { ERR_PACK(0, SUNW_F_FIND_FNAME, 0), "sunw_find_fname" }, + { ERR_PACK(0, SUNW_F_PARSE_OUTER, 0), "parse_outer" }, + { ERR_PACK(0, SUNW_F_CHECKFILE, 0), "checkfile" }, + { 0, NULL } +}; + +/* Error codes and their matching strings */ +static ERR_STRING_DATA SUNW_str_reasons[] = { + { SUNW_R_INVALID_ARG, "invalid argument" }, + { SUNW_R_MEMORY_FAILURE, "memory failure" }, + { SUNW_R_MAC_VERIFY_FAILURE, "mac verify failure" }, + { SUNW_R_MAC_CREATE_FAILURE, "mac create failure" }, + { SUNW_R_BAD_FILETYPE, "bad file type" }, + { SUNW_R_BAD_PKEY, "bad or missing private key" }, + { SUNW_R_BAD_PKEYTYPE, "unsupported key type" }, + { SUNW_R_PKEY_READ_ERR, "unable to read private key" }, + { SUNW_R_NO_TRUST_ANCHOR, "no trust anchors found" }, + { SUNW_R_READ_TRUST_ERR, "unable to read trust anchor" }, + { SUNW_R_ADD_TRUST_ERR, "unable to add trust anchor" }, + { SUNW_R_PKCS12_PARSE_ERR, "PKCS12 parse error" }, + { SUNW_R_PKCS12_CREATE_ERR, "PKCS12 create error" }, + { SUNW_R_BAD_CERTTYPE, "unsupported certificate type" }, + { SUNW_R_PARSE_CERT_ERR, "error parsing PKCS12 certificate" }, + { SUNW_R_PARSE_BAG_ERR, "error parsing PKCS12 bag" }, + { SUNW_R_MAKE_BAG_ERR, "error making PKCS12 bag" }, + { SUNW_R_BAD_LKID, "bad localKeyID format" }, + { SUNW_R_SET_LKID_ERR, "error setting localKeyID" }, + { SUNW_R_BAD_FNAME, "bad friendlyName format" }, + { SUNW_R_SET_FNAME_ERR, "error setting friendlyName" }, + { SUNW_R_BAD_TRUST, "bad or missing trust anchor" }, + { SUNW_R_BAD_BAGTYPE, "unsupported bag type" }, + { SUNW_R_CERT_ERR, "certificate error" }, + { SUNW_R_PKEY_ERR, "private key error" }, + { SUNW_R_READ_ERR, "error reading file" }, + { SUNW_R_ADD_ATTR_ERR, "error adding attribute" }, + { SUNW_R_STR_CONVERT_ERR, "error converting string" }, + { SUNW_R_PKCS12_EMPTY_ERR, "empty PKCS12 structure" }, + { SUNW_R_PASSWORD_ERR, "bad password" }, + { 0, NULL } +}; + +/* + * The library name that our module will be known as. This name + * may be retrieved via OpenSSLs error APIs. + */ +static ERR_STRING_DATA SUNW_lib_name[] = { + { 0, SUNW_LIB_NAME }, + { 0, NULL } +}; +#endif + +/* + * The value of this variable (initialized by a call to + * ERR_load_SUNW_strings()) is what identifies our errors + * to OpenSSL as being ours. + */ +static int SUNW_lib_error_code = 0; + +/* local routines */ +static int parse_pkcs12(PKCS12 *, const char *, int, char *, int, char *, + EVP_PKEY **, X509 **, STACK_OF(X509) **); +static int pem_info(FILE *, pem_password_cb, void *, + STACK_OF(EVP_PKEY) **, STACK_OF(X509) **); + +static int parse_outer(PKCS12 *, const char *, STACK_OF(EVP_PKEY) *, + STACK_OF(X509) *); + +static int parse_all_bags(STACK_OF(PKCS12_SAFEBAG) *, const char *, + STACK_OF(EVP_PKEY) *, STACK_OF(X509) *); + +static int parse_one_bag(PKCS12_SAFEBAG *, const char *, + STACK_OF(EVP_PKEY) *, STACK_OF(X509) *); + +static X509_ATTRIBUTE *type2attrib(ASN1_TYPE *, int); +static ASN1_TYPE *attrib2type(X509_ATTRIBUTE *); +static uchar_t *utf82ascstr(ASN1_UTF8STRING *); +static ASN1_BMPSTRING *asc2bmpstring(const char *, int); +static int find_attr_by_nid(STACK_OF(X509_ATTRIBUTE) *, int); +static int find_attr(int, ASN1_STRING *, STACK_OF(EVP_PKEY) *, + EVP_PKEY **, STACK_OF(X509) *, X509 **); + +static chk_errs_t check_time(chk_actions_t, X509 *); +static int get_key_cert(int, STACK_OF(EVP_PKEY) *, EVP_PKEY **, + STACK_OF(X509) *, X509 **cert); +static int move_certs(STACK_OF(X509) *, STACK_OF(X509) *); +static int sunw_append_keys(STACK_OF(EVP_PKEY) *, + STACK_OF(EVP_PKEY) *); +static int set_results(STACK_OF(EVP_PKEY) **, + STACK_OF(EVP_PKEY) **, STACK_OF(X509) **, STACK_OF(X509) **, + STACK_OF(X509) **, STACK_OF(X509) **, + STACK_OF(EVP_PKEY) **, STACK_OF(EVP_PKEY) **); + +/* + * ---------------------------------------------------------------------------- + * Public routines + * ---------------------------------------------------------------------------- + */ + +/* + * sunw_PKCS12_parse - Parse a PKCS12 structure and break it into its parts. + * + * Parse and decrypt a PKCS#12 structure returning user key, user cert and/or + * other (CA) certs. Note either ca should be NULL, *ca should be NULL, + * or it should point to a valid STACK_OF(X509) structure. pkey and cert can + * be passed uninitialized. + * + * Arguments: + * p12 - Structure with pkcs12 info to be parsed + * pass - Pass phrase for the private key (possibly empty) or NULL if + * there is none. + * matchty - Info about which certs/keys to return if many are in the file. + * keyid - If private key localkeyids friendlynames are to match a + * predetermined value, the value to match. This value should + * be an octet string. + * keyid_len- Length of the keyid byte string. + * name_str - If friendlynames are to match a predetermined value, the value + * to match. This value should be a NULL terminated string. + * pkey - Points to location pointing to the private key returned. + * cert - Points to locaiton which points to the client cert returned + * ca - Points to location that points to a stack of 'certificate + * authority' certs/trust anchors. + * + * Match based on the value of 'matchty' and the contents of 'keyid' + * and/or 'name_str', as appropriate. Go through the lists of certs and + * private keys which were taken from the pkcs12 structure, looking for + * matches of the requested type. This function only searches the lists of + * matching private keys and client certificates. Kinds of matches allowed, + * and the order in which they will be checked, are: + * + * 1) Find the key and/or cert whose localkeyid attributes matches + * 'keyid'. + * 2) Find the key and/or cert whose friendlyname attributes matches + * 'name_str' + * 3) Return the first matching key/cert pair found. + * 4) Return the last matching key/cert pair found. + * 5) Return whatever cert and/or key are available, even unmatching. + * + * Append to the CA list, the certs which do not have matching private + * keys and which were not selected. + * + * If none of the bits are set, no client certs or private keys will be + * returned. CA (aka trust anchor) certs can be. + * + * Notes: If #3 is selected, then #4 will never occur. CA certs will be + * selected after a cert/key pairs are isolated. + * + * Returns: + * < 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * >= 0 - Objects were found and returned. Which objects are indicated by + * which bits are set (FOUND_PKEY, FOUND_CERT, FOUND_CA_CERTS). + */ +int +sunw_PKCS12_parse(PKCS12 *p12, const char *pass, int matchty, char *keyid, + int keyid_len, char *name_str, EVP_PKEY **pkey, X509 **cert, + STACK_OF(X509) **ca) +{ + boolean_t ca_supplied; + int retval = -1; + + /* If NULL PKCS12 structure, this is an error */ + if (p12 == NULL) { + SUNWerr(SUNW_F_PKCS12_PARSE, SUNW_R_INVALID_ARG); + return (-1); + } + + /* Set up arguments.... These will be allocated if needed */ + if (pkey) + *pkey = NULL; + if (cert) + *cert = NULL; + + /* + * If there is already a ca list, use it. Otherwise, allocate one + * and free is later if an error occurs or whatever.) + */ + ca_supplied = (ca != NULL && *ca != NULL); + if (ca != NULL && *ca == NULL) { + if ((*ca = sk_X509_new_null()) == NULL) { + SUNWerr(SUNW_F_PKCS12_PARSE, SUNW_R_MEMORY_FAILURE); + return (-1); + } + } + + /* + * If password is zero length or NULL then try verifying both cases + * to determine which password is correct. The reason for this is that + * under PKCS#12 password based encryption no password and a zero + * length password are two different things. If the password has a + * non-zero length and is not NULL then call PKCS12_verify_mac() with + * a length of '-1' and let it use strlen() to figure out the length + * of the password. + */ + /* Check the mac */ + if (pass == NULL || *pass == '\0') { + if (PKCS12_verify_mac(p12, NULL, 0)) + pass = NULL; + else if (PKCS12_verify_mac(p12, "", 0)) + pass = ""; + else { + SUNWerr(SUNW_F_PKCS12_PARSE, + SUNW_R_MAC_VERIFY_FAILURE); + goto err; + } + } else if (PKCS12_verify_mac(p12, pass, -1) == 0) { + SUNWerr(SUNW_F_PKCS12_PARSE, SUNW_R_MAC_VERIFY_FAILURE); + goto err; + } + + retval = parse_pkcs12(p12, pass, matchty, keyid, keyid_len, + name_str, pkey, cert, ca); + if (retval < 0) { + SUNWerr(SUNW_F_PKCS12_PARSE, SUNW_R_PKCS12_PARSE_ERR); + goto err; + } + return (retval); + +err: + if (pkey && *pkey) { + sunw_evp_pkey_free(*pkey); + } + if (cert && *cert) + X509_free(*cert); + if (ca_supplied == B_FALSE && ca != NULL) + sk_X509_pop_free(*ca, X509_free); + + return (-1); + +} + + +/* + * sunw_PEM_contents() parses a PEM file and returns component parts found + * + * Parse and decrypt a PEM file, returning any user keys and certs. + * + * There are some limits to this function. It will ignore the following: + * - certificates identified by "TRUSTED CERTIFICATE" + * - CERTIFICATE REQUEST and NEW CERTIFICATE REQUEST records. + * - X509 CRL + * - DH PARAMETERS + * - DSA PARAMETERS + * - Any PUBLIC KEY + * - PKCS7 + * - PRIVATE KEY or ENCRYPTED PRIVATE KEY (PKCS 8) + * + * Arguments: + * fp - File pointer for file containing PEM data. + * pass - Pass phrase for the private key or NULL if there is none. + * pkeys - Points to address of a stack of private keys to return. + * certs - Points to address of a stack of client certs to return. + * + * The pointers to stacks should either be NULL or their contents should + * either be NULL or should point to a valid STACK_OF(X509) structure. + * If the stacks contain information, corresponding information from the + * file will be appended to the original contents. + * + * Note: Client certs and and their matching private keys will be in any + * order. + * + * Certs which have no matching private key are assumed to be ca certs. + * + * Returns: + * < 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * >= 0 - Objects were found and returned. Which objects are indicated by + * which bits are set (FOUND_PKEY, FOUND_CERT) + */ +int sunw_PEM_contents(FILE *fp, pem_password_cb *cb, void *userdata, + STACK_OF(EVP_PKEY) **pkey, STACK_OF(X509) **certs) +{ + STACK_OF(EVP_PKEY) *work_kl = NULL; + STACK_OF(X509) *work_ca = NULL; + int retval = -1; + + /* + * Allocate the working stacks for private key and for the + * ca certs. + */ + if ((work_kl = sk_EVP_PKEY_new_null()) == NULL) { + SUNWerr(SUNW_F_PEM_CONTENTS, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + if ((work_ca = sk_X509_new_null()) == NULL) { + SUNWerr(SUNW_F_PEM_CONTENTS, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + /* Error strings are set within the following. */ + if (pem_info(fp, cb, userdata, &work_kl, &work_ca) <= 0) { + goto cleanup; + } + + /* on error, set_results() returns an error on the stack */ + retval = set_results(pkey, &work_kl, certs, &work_ca, NULL, NULL, NULL, + NULL); +cleanup: + if (work_kl != NULL) { + sk_EVP_PKEY_pop_free(work_kl, sunw_evp_pkey_free); + } + if (work_ca != NULL) + sk_X509_pop_free(work_ca, X509_free); + + return (retval); +} + + +/* + * sunw_PKCS12_contents() parses a pkcs#12 structure and returns component + * parts found, without evaluation. + * + * Parse and decrypt a PKCS#12 structure returning any user keys and/or + * various certs. Note these should either be NULL, *whatever should + * be NULL, or it should point to a valid STACK_OF(X509) structure. + * + * Arguments: + * p12 - Structure with pkcs12 info to be parsed + * pass - Pass phrase for the private key and entire pkcs12 wad (possibly + * empty) or NULL if there is none. + * pkeys - Points to address of a stack of private keys to return. + * certs - Points to address of a stack of client certs return. + * + * Note: The certs and keys being returned are in random order. + * + * Returns: + * < 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * >= 0 - Objects were found and returned. Which objects are indicated by + * which bits are set (FOUND_PKEY or FOUND_CERT) + */ +int +sunw_PKCS12_contents(PKCS12 *p12, const char *pass, STACK_OF(EVP_PKEY) **pkey, + STACK_OF(X509) **certs) +{ + STACK_OF(EVP_PKEY) *work_kl = NULL; + STACK_OF(X509) *work_ca = NULL; + int retval = -1; + + /* + * Allocate the working stacks for private key and for the + * ca certs. + */ + if ((work_kl = sk_EVP_PKEY_new_null()) == NULL) { + SUNWerr(SUNW_F_PKCS12_CONTENTS, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + if ((work_ca = sk_X509_new_null()) == NULL) { + SUNWerr(SUNW_F_PKCS12_CONTENTS, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + if (parse_outer(p12, pass, work_kl, work_ca) == 0) { + /* + * Error already on stack + */ + goto cleanup; + } + + /* on error, set_results() returns an error on the stack */ + retval = set_results(pkey, &work_kl, certs, &work_ca, NULL, + NULL, NULL, NULL); + +cleanup: + if (work_kl != NULL) { + sk_EVP_PKEY_pop_free(work_kl, sunw_evp_pkey_free); + } + + return (retval); +} + + + +/* + * sunw_split_certs() - Given a list of certs and a list of private keys, + * moves certs which match one of the keys to a different stack. + * + * Arguments: + * allkeys - Points to a stack of private keys to search. + * allcerts - Points to a stack of certs to be searched. + * keycerts - Points to address of a stack of certs with matching private + * keys. They are moved from 'allcerts'. This may not be NULL + * when called. If *keycerts is NULL upon entry, a new stack will + * be allocated. Otherwise, it must be a valid STACK_OF(509). + * nocerts - Points to address of a stack for keys which have no matching + * certs. Keys are moved from 'allkeys' here when they have no + * matching certs. If this is NULL, matchless keys will be + * discarded. + * + * Notes: If an error occurs while moving certs, the cert being move may be + * lost. 'keycerts' may only contain part of the matching certs. The number + * of certs successfully moved can be found by checking sk_X509_num(keycerts). + * + * If there is a key which does not have a matching cert, it is moved to + * the list nocerts. + * + * If all certs are removed from 'certs' and/or 'pkeys', it will be the + * caller's responsibility to free the empty stacks. + * + * Returns: + * < 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * >= 0 - The number of certs moved from 'cert' to 'pkcerts'. + */ +int +sunw_split_certs(STACK_OF(EVP_PKEY) *allkeys, STACK_OF(X509) *allcerts, + STACK_OF(X509) **keycerts, STACK_OF(EVP_PKEY) **nocerts) +{ + STACK_OF(X509) *matching; + STACK_OF(EVP_PKEY) *nomatch; + EVP_PKEY *tmpkey; + X509 *tmpcert; + int count = 0; + int found; + int res; + int i; + int k; + + *keycerts = NULL; + if (nocerts != NULL) + *nocerts = NULL; + nomatch = NULL; + + if ((matching = sk_X509_new_null()) == NULL) { + SUNWerr(SUNW_F_SPLIT_CERTS, SUNW_R_MEMORY_FAILURE); + return (-1); + } + *keycerts = matching; + + k = 0; + while (k < sk_EVP_PKEY_num(allkeys)) { + found = 0; + tmpkey = sk_EVP_PKEY_value(allkeys, k); + + for (i = 0; i < sk_X509_num(allcerts); i++) { + tmpcert = sk_X509_value(allcerts, i); + res = X509_check_private_key(tmpcert, tmpkey); + if (res != 0) { + count++; + found = 1; + tmpcert = sk_X509_delete(allcerts, i); + if (sk_X509_push(matching, tmpcert) == 0) { + X509_free(tmpcert); + SUNWerr(SUNW_F_SPLIT_CERTS, + SUNW_R_MEMORY_FAILURE); + return (-1); + } + break; + } + } + if (found != 0) { + /* + * Found a match - keep the key & check out the next + * one. + */ + k++; + } else { + /* + * No cert matching this key. Move the key if + * possible or discard it. Don't increment the + * index. + */ + if (nocerts == NULL) { + tmpkey = sk_EVP_PKEY_delete(allkeys, k); + sunw_evp_pkey_free(tmpkey); + } else { + if (*nocerts == NULL) { + nomatch = sk_EVP_PKEY_new_null(); + if (nomatch == NULL) { + SUNWerr(SUNW_F_SPLIT_CERTS, + SUNW_R_MEMORY_FAILURE); + return (-1); + } + *nocerts = nomatch; + } + tmpkey = sk_EVP_PKEY_delete(allkeys, k); + if (sk_EVP_PKEY_push(nomatch, tmpkey) == 0) { + sunw_evp_pkey_free(tmpkey); + SUNWerr(SUNW_F_SPLIT_CERTS, + SUNW_R_MEMORY_FAILURE); + return (-1); + } + } + } + } + + return (count); +} + +/* + * sunw_PKCS12_create() creates a pkcs#12 structure and given component parts. + * + * Given one or more of user private key, user cert and/or other (CA) certs, + * return an encrypted PKCS12 structure containing them. + * + * Arguments: + * pass - Pass phrase for the pkcs12 structure and private key (possibly + * empty) or NULL if there is none. It will be used to encrypt + * both the private key(s) and as the pass phrase for the whole + * pkcs12 wad. + * pkeys - Points to stack of private keys. + * certs - Points to stack of client (public ke) certs + * cacerts - Points to stack of 'certificate authority' certs (or trust + * anchors). + * + * Note that any of these may be NULL. + * + * Returns: + * NULL - An error occurred. + * != NULL - Address of PKCS12 structure. The user is responsible for + * freeing the memory when done. + */ +PKCS12 * +sunw_PKCS12_create(const char *pass, STACK_OF(EVP_PKEY) *pkeys, + STACK_OF(X509) *certs, STACK_OF(X509) *cacerts) +{ + int nid_cert = NID_pbe_WithSHA1And40BitRC2_CBC; + int nid_key = NID_pbe_WithSHA1And3_Key_TripleDES_CBC; + STACK_OF(PKCS12_SAFEBAG) *bags = NULL; + STACK_OF(PKCS7) *safes = NULL; + PKCS12_SAFEBAG *bag = NULL; + PKCS8_PRIV_KEY_INFO *p8 = NULL; + EVP_PKEY *pkey = NULL; + PKCS12 *ret_p12 = NULL; + PKCS12 *p12 = NULL; + PKCS7 *authsafe = NULL; + X509 *cert = NULL; + uchar_t *str = NULL; + int certs_there = 0; + int keys_there = 0; + int len; + int i; + + if ((safes = sk_PKCS7_new_null()) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_MEMORY_FAILURE); + return (NULL); + } + + if ((bags = sk_PKCS12_SAFEBAG_new_null()) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_MEMORY_FAILURE); + goto err_ret; + } + + if (certs != NULL && sk_X509_num(certs) > 0) { + + for (i = 0; i < sk_X509_num(certs); i++) { + cert = sk_X509_value(certs, i); + + /* Add user certificate */ + if ((bag = M_PKCS12_x5092certbag(cert)) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_CERT_ERR); + goto err_ret; + } + if (cert->aux != NULL && cert->aux->alias != NULL && + cert->aux->alias->type == V_ASN1_UTF8STRING) { + str = utf82ascstr(cert->aux->alias); + if (str == NULL) { + /* + * Error already on stack + */ + goto err_ret; + } + if (PKCS12_add_friendlyname_asc(bag, + (char const *) str, + strlen((char const *) str)) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_ADD_ATTR_ERR); + goto err_ret; + } + } + if (cert->aux != NULL && cert->aux->keyid != NULL && + cert->aux->keyid->type == V_ASN1_OCTET_STRING) { + str = cert->aux->keyid->data; + len = cert->aux->keyid->length; + + if (str != NULL && + PKCS12_add_localkeyid(bag, str, len) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_ADD_ATTR_ERR); + goto err_ret; + } + } + if (sk_PKCS12_SAFEBAG_push(bags, bag) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_MEMORY_FAILURE); + goto err_ret; + } + certs_there++; + bag = NULL; + } + } + + if (cacerts != NULL && sk_X509_num(cacerts) > 0) { + + /* Put all certs in structure */ + for (i = 0; i < sk_X509_num(cacerts); i++) { + cert = sk_X509_value(cacerts, i); + if ((bag = M_PKCS12_x5092certbag(cert)) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_CERT_ERR); + goto err_ret; + } + + if (cert->aux != NULL && cert->aux->alias != NULL && + cert->aux->alias->type == V_ASN1_UTF8STRING) { + str = utf82ascstr(cert->aux->alias); + if (str == NULL) { + /* + * Error already on stack + */ + goto err_ret; + } + if (PKCS12_add_friendlyname_asc( + bag, (char const *) str, + strlen((char const *) str)) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_ADD_ATTR_ERR); + goto err_ret; + } + } + if (cert->aux != NULL && cert->aux->keyid != NULL && + cert->aux->keyid->type == V_ASN1_OCTET_STRING) { + str = cert->aux->keyid->data; + len = cert->aux->keyid->length; + + if (str != NULL && + PKCS12_add_localkeyid(bag, str, len) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_ADD_ATTR_ERR); + goto err_ret; + } + } + if (sk_PKCS12_SAFEBAG_push(bags, bag) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_MEMORY_FAILURE); + goto err_ret; + } + certs_there++; + bag = NULL; + } + } + + if (certs != NULL || cacerts != NULL && certs_there) { + /* Turn certbags into encrypted authsafe */ + authsafe = PKCS12_pack_p7encdata(nid_cert, pass, -1, + NULL, 0, PKCS12_DEFAULT_ITER, bags); + if (authsafe == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_CERT_ERR); + goto err_ret; + } + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + bags = NULL; + + if (sk_PKCS7_push(safes, authsafe) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_MEMORY_FAILURE); + goto err_ret; + } + authsafe = NULL; + } + + if (pkeys != NULL && sk_EVP_PKEY_num(pkeys) > 0) { + + if (bags == NULL && + (bags = sk_PKCS12_SAFEBAG_new_null()) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_MEMORY_FAILURE); + goto err_ret; + } + + for (i = 0; i < sk_EVP_PKEY_num(pkeys); i++) { + + pkey = sk_EVP_PKEY_value(pkeys, i); + + /* Make a shrouded key bag */ + if ((p8 = EVP_PKEY2PKCS8(pkey)) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_PKEY_ERR); + goto err_ret; + } + + bag = PKCS12_MAKE_SHKEYBAG(nid_key, pass, -1, NULL, 0, + PKCS12_DEFAULT_ITER, p8); + if (bag == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_MAKE_BAG_ERR); + goto err_ret; + } + PKCS8_PRIV_KEY_INFO_free(p8); + p8 = NULL; + + len = sunw_get_pkey_fname(GETDO_COPY, pkey, + (char **)&str); + if (str != NULL) { + if (PKCS12_add_friendlyname_asc(bag, + (const char *)str, len) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_ADD_ATTR_ERR); + goto err_ret; + } + } + str = NULL; + + len = sunw_get_pkey_localkeyid(GETDO_COPY, pkey, + (char **)&str, &len); + if (str != NULL) { + if (PKCS12_add_localkeyid(bag, str, len) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_ADD_ATTR_ERR); + goto err_ret; + } + } + str = NULL; + + if (sk_PKCS12_SAFEBAG_push(bags, bag) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_MEMORY_FAILURE); + goto err_ret; + } + keys_there++; + bag = NULL; + } + + if (keys_there) { + /* Turn into unencrypted authsafe */ + authsafe = PKCS12_pack_p7data(bags); + if (authsafe == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_PKCS12_CREATE_ERR); + goto err_ret; + } + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + bags = NULL; + + if (sk_PKCS7_push(safes, authsafe) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, + SUNW_R_MEMORY_FAILURE); + } + authsafe = NULL; + } + } + + if (certs_there == 0 && keys_there == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_PKCS12_EMPTY_ERR); + goto err_ret; + } + + if ((p12 = PKCS12_init(NID_pkcs7_data)) == NULL) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_PKCS12_CREATE_ERR); + goto err_ret; + } + + /* + * Note that safes is copied by the following. Therefore, it needs + * to be freed whether or not the following succeeds. + */ + if (M_PKCS12_pack_authsafes(p12, safes) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_PKCS12_CREATE_ERR); + goto err_ret; + } + if (PKCS12_set_mac(p12, pass, -1, NULL, 0, 2048, NULL) == 0) { + SUNWerr(SUNW_F_PKCS12_CREATE, SUNW_R_MAC_CREATE_FAILURE); + goto err_ret; + } + + ret_p12 = p12; + p12 = NULL; + + /* Fallthrough is intentional */ + +err_ret: + + if (str != NULL) + free(str); + + if (p8 != NULL) + PKCS8_PRIV_KEY_INFO_free(p8); + + if (bag != NULL) + PKCS12_SAFEBAG_free(bag); + if (bags != NULL) + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + if (authsafe != NULL) + PKCS7_free(authsafe); + if (safes != NULL) + sk_PKCS7_pop_free(safes, PKCS7_free); + if (p12 != NULL) + PKCS12_free(p12); + + return (ret_p12); +} + +/* + * sunw_evp_pkey_free() Given an EVP_PKEY structure, free any attributes + * that are attached. Then free the EVP_PKEY itself. + * + * This is a replacement for EVP_PKEY_free() for the sunw stuff. + * It should be used in places where EVP_PKEY_free would be used, + * including calls to sk_EVP_PKEY_pop_free(). + * + * Arguments: + * pkey - Entry which potentially has attributes to be freed. + * + * Returns: + * None. + */ +void +sunw_evp_pkey_free(EVP_PKEY *pkey) +{ + if (pkey != NULL) { + if (pkey->attributes != NULL) { + sk_X509_ATTRIBUTE_pop_free(pkey->attributes, + X509_ATTRIBUTE_free); + pkey->attributes = NULL; + } + EVP_PKEY_free(pkey); + } +} + +/* + * sunw_set_localkeyid() sets the localkeyid in a cert, a private key or + * both. Any existing localkeyid will be discarded. + * + * Arguments: + * keyid_str- A byte string with the localkeyid to set + * keyid_len- Length of the keyid byte string. + * pkey - Points to a private key to set the keyidstr in. + * cert - Points to a cert to set the keyidstr in. + * + * Note that setting a keyid into a cert which will not be written out as + * a PKCS12 cert is pointless since it will be lost. + * + * Returns: + * 0 - Success. + * < 0 - An error occurred. It was probably an error in allocating + * memory. The error will be set in the error stack. Call + * ERR_get_error() to get specific information. + */ +int +sunw_set_localkeyid(const char *keyid_str, int keyid_len, EVP_PKEY *pkey, + X509 *cert) +{ + X509_ATTRIBUTE *attr = NULL; + ASN1_STRING *str = NULL; + ASN1_TYPE *keyid = NULL; + int retval = -1; + int i; + + if (cert != NULL) { + if (X509_keyid_set1(cert, (uchar_t *)keyid_str, keyid_len) + == 0) { + SUNWerr(SUNW_F_SET_LOCALKEYID, SUNW_R_SET_LKID_ERR); + goto cleanup; + } + } + if (pkey != NULL) { + str = (ASN1_STRING *)M_ASN1_OCTET_STRING_new(); + if (str == NULL || + M_ASN1_OCTET_STRING_set(str, keyid_str, keyid_len) == 0 || + (keyid = ASN1_TYPE_new()) == NULL) { + SUNWerr(SUNW_F_SET_LOCALKEYID, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + ASN1_TYPE_set(keyid, V_ASN1_OCTET_STRING, str); + str = NULL; + + attr = type2attrib(keyid, NID_localKeyID); + if (attr == NULL) { + /* + * Error already on stack + */ + goto cleanup; + } + keyid = NULL; + + if (pkey->attributes == NULL) { + pkey->attributes = sk_X509_ATTRIBUTE_new_null(); + if (pkey->attributes == NULL) { + SUNWerr(SUNW_F_SET_LOCALKEYID, + SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + } else { + i = find_attr_by_nid(pkey->attributes, NID_localKeyID); + if (i >= 0) + sk_X509_ATTRIBUTE_delete(pkey->attributes, i); + } + if (sk_X509_ATTRIBUTE_push(pkey->attributes, attr) == 0) { + SUNWerr(SUNW_F_SET_LOCALKEYID, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + attr = NULL; + } + retval = 0; + +cleanup: + if (str != NULL) + ASN1_STRING_free(str); + if (keyid != NULL) + ASN1_TYPE_free(keyid); + if (attr != NULL) + X509_ATTRIBUTE_free(attr); + + return (retval); +} + +/* + * sunw_get_pkey_localkeyid() gets the localkeyid from a private key. It can + * optionally remove the value found. + * + * Arguments: + * dowhat - What to do with the attributes (remove them or copy them). + * pkey - Points to a private key to set the keyidstr in. + * keyid_str- Points to a location which will receive the pointer to + * a byte string containing the binary localkeyid. Note that + * this is a copy, and the caller must free it. + * keyid_len- Length of keyid_str. + * + * Returns: + * >= 0 - The number of characters in the keyid returned. + * < 0 - An error occurred. It was probably an error in allocating + * memory. The error will be set in the error stack. Call + * ERR_get_error() to get specific information. + */ +int +sunw_get_pkey_localkeyid(getdo_actions_t dowhat, EVP_PKEY *pkey, + char **keyid_str, int *keyid_len) +{ + X509_ATTRIBUTE *attr = NULL; + ASN1_OCTET_STRING *str = NULL; + ASN1_TYPE *ty = NULL; + int len = 0; + int i; + + if (keyid_str != NULL) + *keyid_str = NULL; + if (keyid_len != NULL) + *keyid_len = 0; + + if (pkey == NULL || pkey->attributes == NULL) { + return (0); + } + + if ((i = find_attr_by_nid(pkey->attributes, NID_localKeyID)) < 0) { + return (0); + } + attr = sk_X509_ATTRIBUTE_value(pkey->attributes, i); + + if ((ty = attrib2type(attr)) == NULL || + ty->type != V_ASN1_OCTET_STRING) { + return (0); + } + + if (dowhat == GETDO_DEL) { + attr = sk_X509_ATTRIBUTE_delete(pkey->attributes, i); + if (attr != NULL) + X509_ATTRIBUTE_free(attr); + return (0); + } + + str = ty->value.octet_string; + len = str->length; + if ((*keyid_str = malloc(len)) == NULL) { + SUNWerr(SUNW_F_GET_LOCALKEYID, SUNW_R_MEMORY_FAILURE); + return (-1); + } + + (void) memcpy(*keyid_str, str->data, len); + *keyid_len = len; + + return (len); +} + +/* + * sunw_get_pkey_fname() gets the friendlyName from a private key. It can + * optionally remove the value found. + * + * Arguments: + * dowhat - What to do with the attributes (remove them or copy them). + * pkey - Points to a private key to get the frientlyname from + * fname - Points to a location which will receive the pointer to a + * byte string with the ASCII friendlyname + * + * Returns: + * >= 0 - The number of characters in the frienlyname returned. + * < 0 - An error occurred. It was probably an error in allocating + * memory. The error will be set in the error stack. Call + * ERR_get_error() to get specific information. + */ +int +sunw_get_pkey_fname(getdo_actions_t dowhat, EVP_PKEY *pkey, char **fname) +{ + X509_ATTRIBUTE *attr = NULL; + ASN1_BMPSTRING *str = NULL; + ASN1_TYPE *ty = NULL; + int len = 0; + int i; + + if (fname != NULL) + *fname = NULL; + + if (pkey == NULL || pkey->attributes == NULL) { + return (0); + } + + if ((i = find_attr_by_nid(pkey->attributes, NID_friendlyName)) < 0) { + return (0); + } + attr = sk_X509_ATTRIBUTE_value(pkey->attributes, i); + + if ((ty = attrib2type(attr)) == NULL || + ty->type != V_ASN1_BMPSTRING) { + return (0); + } + + if (dowhat == GETDO_DEL) { + attr = sk_X509_ATTRIBUTE_delete(pkey->attributes, i); + if (attr != NULL) + X509_ATTRIBUTE_free(attr); + return (0); + } + + str = ty->value.bmpstring; + *fname = uni2asc(str->data, str->length); + if (*fname == NULL) { + SUNWerr(SUNW_F_GET_PKEY_FNAME, SUNW_R_MEMORY_FAILURE); + return (-1); + } + + len = strlen(*fname); + + return (len); +} + +/* + * sunw_find_localkeyid() searches stacks of certs and private keys, + * and returns the first matching cert/private key found. + * + * Look for a keyid in a stack of certs. if 'certs' is NULL and 'pkeys' is + * not NULL, search the list of private keys. Move the matching cert to + * 'matching_cert' and its matching private key to 'matching_pkey'. If no + * cert or keys match, no match occurred. + * + * Arguments: + * keyid_str- A byte string with the localkeyid to match + * keyid_len- Length of the keyid byte string. + * pkeys - Points to a stack of private keys which match the certs. + * This may be NULL, in which case no keys are returned. + * certs - Points to a stack of certs to search. If NULL, search the + * stack of keys instead. + * matching_pkey + * - Pointer to receive address of first matching pkey found. + * 'matching_pkey' must not be NULL; '*matching_pkey' will be + * reset. + * matching_cert + * - Pointer to receive address of first matching cert found. + * 'matching_cert' must not be NULL; '*matching_cert' will be + * reset. + * + * Returns: + * < 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * >= 0 - Objects were found and returned. Which objects are indicated by + * which bits are set (FOUND_PKEY and/or FOUND_CERT). + */ +int +sunw_find_localkeyid(char *keyid_str, int len, STACK_OF(EVP_PKEY) *pkeys, +STACK_OF(X509) *certs, EVP_PKEY **matching_pkey, X509 **matching_cert) +{ + ASN1_STRING *cmpstr = NULL; + EVP_PKEY *tmp_pkey = NULL; + X509 *tmp_cert = NULL; + int retval = 0; + + /* If NULL arguments, this is an error */ + if (keyid_str == NULL || + (pkeys == NULL || certs == NULL) || + (pkeys != NULL && matching_pkey == NULL) || + (certs != NULL && matching_cert == NULL)) { + SUNWerr(SUNW_F_FIND_LOCALKEYID, SUNW_R_INVALID_ARG); + return (-1); + } + + if (matching_pkey != NULL) + *matching_pkey = NULL; + if (matching_cert != NULL) + *matching_cert = NULL; + + cmpstr = (ASN1_STRING *)M_ASN1_OCTET_STRING_new(); + if (cmpstr == NULL || + M_ASN1_OCTET_STRING_set(cmpstr, keyid_str, len) == 0) { + SUNWerr(SUNW_F_FIND_LOCALKEYID, SUNW_R_MEMORY_FAILURE); + return (-1); + } + + retval = find_attr(NID_localKeyID, cmpstr, pkeys, &tmp_pkey, certs, + &tmp_cert); + if (retval == 0) { + ASN1_STRING_free(cmpstr); + return (retval); + } + + if (matching_pkey != NULL) + *matching_pkey = tmp_pkey; + if (matching_cert != NULL) + *matching_cert = tmp_cert; + + return (retval); +} + +/* + * sunw_find_fname() searches stacks of certs and private keys for one with + * a matching friendlyname and returns the first matching cert/private + * key found. + * + * Look for a friendlyname in a stack of certs. if 'certs' is NULL and 'pkeys' + * is not NULL, search the list of private keys. Move the matching cert to + * 'matching_cert' and its matching private key to 'matching_pkey'. If no + * cert or keys match, no match occurred. + * + * Arguments: + * fname - Friendlyname to find (NULL-terminated ASCII string). + * pkeys - Points to a stack of private keys which match the certs. + * This may be NULL, in which case no keys are returned. + * certs - Points to a stack of certs to search. If NULL, search the + * stack of keys instead. + * matching_pkey + * - Pointer to receive address of first matching pkey found. + * matching_cert + * - Pointer to receive address of first matching cert found. + * + * Returns: + * < 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * >= 0 - Objects were found and returned. Which objects are indicated by + * which bits are set (FOUND_PKEY and/or FOUND_CERT). + */ +int +sunw_find_fname(char *fname, STACK_OF(EVP_PKEY) *pkeys, STACK_OF(X509) *certs, + EVP_PKEY **matching_pkey, X509 ** matching_cert) +{ + ASN1_STRING *cmpstr = NULL; + EVP_PKEY *tmp_pkey = NULL; + X509 *tmp_cert = NULL; + int retval = 0; + + /* If NULL arguments, this is an error */ + if (fname == NULL || + (pkeys == NULL && certs == NULL) || + (pkeys != NULL && matching_pkey == NULL) || + (certs != NULL && matching_cert == NULL)) { + SUNWerr(SUNW_F_FIND_FNAME, SUNW_R_INVALID_ARG); + return (-1); + } + + if (matching_pkey != NULL) + *matching_pkey = NULL; + if (matching_cert != NULL) + *matching_cert = NULL; + + cmpstr = (ASN1_STRING *)asc2bmpstring(fname, strlen(fname)); + if (cmpstr == NULL) { + /* + * Error already on stack + */ + return (-1); + } + + retval = find_attr(NID_friendlyName, cmpstr, pkeys, &tmp_pkey, certs, + &tmp_cert); + if (retval == 0) { + ASN1_STRING_free(cmpstr); + return (retval); + } + + if (matching_pkey != NULL) + *matching_pkey = tmp_pkey; + if (matching_cert != NULL) + *matching_cert = tmp_cert; + + return (retval); +} + +/* + * sunw_get_cert_fname() gets the fiendlyname from a cert. It can + * optionally remove the value found. + * + * Arguments: + * dowhat - What to do with the attributes (remove them or copy them). + * cert - Points to a cert to get the friendlyName from. + * fname - Points to a location which will receive the pointer to a + * byte string with the ASCII friendlyname + * + * Returns: + * >= 0 - The number of characters in the friendlyname returned. + * < 0 - An error occurred. It was probably an error in allocating + * memory. The error will be set in the error stack. Call + * ERR_get_error() to get specific information. + */ +int +sunw_get_cert_fname(getdo_actions_t dowhat, X509 *cert, char **fname) +{ + int len; + + if (fname != NULL) + *fname = NULL; + + if (cert == NULL || cert->aux == NULL || cert->aux->alias == NULL) { + return (0); + } + + if (dowhat == GETDO_DEL) { + /* Delete the entry */ + ASN1_UTF8STRING_free(cert->aux->alias); + cert->aux->alias = NULL; + return (0); + } + + *((uchar_t **)fname) = utf82ascstr(cert->aux->alias); + if (*fname == NULL) { + /* + * Error already on stack + */ + return (-1); + } + + len = strlen(*fname); + + return (len); +} + +/* + * sunw_set_fname() sets the friendlyName in a cert, a private key or + * both. Any existing friendlyname will be discarded. + * + * Arguments: + * ascname - An ASCII string with the friendlyName to set + * pkey - Points to a private key to set the fname in. + * cert - Points to a cert to set the fname in. + * + * Note that setting a friendlyName into a cert which will not be written out + * as a PKCS12 cert is pointless since it will be lost. + * + * Returns: + * 0 - Success. + * <0 - An error occurred. It was probably an error in allocating + * memory. The error will be set in the error stack. Call + * ERR_get_error() to get specific information. + */ +int +sunw_set_fname(const char *ascname, EVP_PKEY *pkey, X509 *cert) +{ + X509_ATTRIBUTE *attr = NULL; + ASN1_BMPSTRING *str = NULL; + ASN1_TYPE *fname = NULL; + unsigned char *data = NULL; + int retval = -1; + int len; + int i; + + str = asc2bmpstring(ascname, strlen(ascname)); + if (str == NULL) { + /* + * Error already on stack + */ + return (-1); + } + + if (cert != NULL) { + if (cert->aux != NULL && cert->aux->alias != NULL) { + ASN1_UTF8STRING_free(cert->aux->alias); + } + + len = ASN1_STRING_to_UTF8(&data, str); + i = -23; + if (len <= 0 || (i = X509_alias_set1(cert, data, len)) == 0) { + SUNWerr(SUNW_F_SET_FNAME, SUNW_R_SET_FNAME_ERR); + goto cleanup; + } + } + if (pkey != NULL) { + if ((fname = ASN1_TYPE_new()) == NULL) { + SUNWerr(SUNW_F_SET_FNAME, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + ASN1_TYPE_set(fname, V_ASN1_BMPSTRING, str); + str = NULL; + + attr = type2attrib(fname, NID_friendlyName); + if (attr == NULL) { + /* + * Error already on stack + */ + goto cleanup; + } + fname = NULL; + + if (pkey->attributes == NULL) { + pkey->attributes = sk_X509_ATTRIBUTE_new_null(); + if (pkey->attributes == NULL) { + SUNWerr(SUNW_F_SET_FNAME, + SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + } else if ((i = find_attr_by_nid(pkey->attributes, + NID_friendlyName)) >= 0) { + (void) sk_X509_ATTRIBUTE_delete(pkey->attributes, i); + } + + if (sk_X509_ATTRIBUTE_push(pkey->attributes, attr) == 0) { + SUNWerr(SUNW_F_SET_FNAME, SUNW_R_MEMORY_FAILURE); + goto cleanup; + } + + attr = NULL; + } + retval = 0; + +cleanup: + if (data != NULL) + OPENSSL_free(data); + if (str != NULL) + ASN1_BMPSTRING_free(str); + if (fname != NULL) + ASN1_TYPE_free(fname); + if (attr != NULL) + X509_ATTRIBUTE_free(attr); + + return (retval); +} + +/* + * sunw_check_keys() compares the public key in the certificate and a + * private key to ensure that they match. + * + * Arguments: + * cert - Points to a certificate. + * pkey - Points to a private key. + * + * Returns: + * == 0 - These do not match. + * != 0 - The cert's public key and the private key match. + */ +int +sunw_check_keys(X509 *cert, EVP_PKEY *pkey) +{ + int retval = 0; + + if (pkey != NULL && cert != NULL) + retval = X509_check_private_key(cert, pkey); + + return (retval); +} + +/* + * sunw_check_cert_times() compares the time fields in a certificate + * + * Compare the 'not before' and the 'not after' times in the cert + * to the current time. Return the results of the comparison (bad time formats, + * cert not yet in force, cert expired or in range) + * + * Arguments: + * dowhat - what field(s) to check. + * cert - Points to a cert to check + * + * Returns: + * Results of the comparison. + */ +chk_errs_t +sunw_check_cert_times(chk_actions_t chkwhat, X509 *cert) +{ + return (check_time(chkwhat, cert)); +} + +/* + * ---------------------------------------------------------------------------- + * Local routines + * ---------------------------------------------------------------------------- + */ + + +/* + * parse_pkcs12 - Oversee parsing of the pkcs12 structure. Get it + * parsed. After that either return what's found directly, or + * do any required matching. + * + * Arguments: + * p12 - Structure with pkcs12 info to be parsed + * pass - Pass phrase for the private key (possibly empty) or NULL if + * there is none. + * matchty - Info about which certs/keys to return if many are in the file. + * keyid - If private key localkeyids friendlynames are to match a + * predetermined value, the value to match. This value should + * be an octet string. + * keyid_len- Length of the keyid byte string. + * name_str - If friendlynames are to match a predetermined value, the value + * to match. This value should be a NULL terminated string. + * pkey - Points to location pointing to the private key returned. + * cert - Points to locaiton which points to the client cert returned + * ca - Points to location that points to a stack of 'certificate + * authority' certs/trust anchors. + * + * Note about error codes: This function is an internal function, and the + * place where it is called sets error codes. Therefore only set an error + * code if it is something that is unique or if the function which detected + * the error doesn't set one. + * + * Returns: + * == -1 - An error occurred. Call ERR_get_error() to get error information. + * Where possible, memory has been freed. + * == 0 - No matching returns were found. + * > 0 - This is the aithmetic 'or' of the FOUND_* bits that indicate which + * of the requested entries were found. + */ +static int +parse_pkcs12(PKCS12 *p12, const char *pass, int matchty, char *keyid, + int kstr_len, char *name_str, EVP_PKEY **pkey, X509 **cert, + STACK_OF(X509) **ca) +{ + STACK_OF(EVP_PKEY) *work_kl = NULL; /* Head for private key list */ + STACK_OF(EVP_PKEY) *nocerts = NULL; /* Head for alt. key list */ + STACK_OF(X509) *work_ca = NULL; /* Head for cert list */ + STACK_OF(X509) *work_cl = NULL; + int retval = 0; + int n; + + retval = sunw_PKCS12_contents(p12, pass, &work_kl, &work_ca); + if (retval < 0) { + goto cleanup; + } else if (retval == 0) { + /* + * Not really an error here - its just that nothing was found. + */ + goto cleanup; + } + + if (sk_EVP_PKEY_num(work_kl) > 0) { + + if (sunw_split_certs(work_kl, work_ca, &work_cl, &nocerts) + < 0) { + goto cleanup; + } + } + + /* + * Go through the lists of certs and private keys which were + * returned, looking for matches of the appropriate type. Do these + * in the order described above. + */ + if ((matchty & DO_FIND_KEYID) != 0) { + + if (keyid == NULL) { + SUNWerr(SUNW_F_PKCS12_PARSE, SUNW_R_INVALID_ARG); + retval = -1; + goto cleanup; + } + + /* See if string matches localkeyid's */ + retval = sunw_find_localkeyid(keyid, kstr_len, + work_kl, work_cl, pkey, cert); + if (retval != 0) { + if (retval == -1) + goto cleanup; + else + goto last_part; + } + } + if ((matchty & DO_FIND_FN) != 0) { + + if (name_str == NULL) { + SUNWerr(SUNW_F_PKCS12_PARSE, SUNW_R_INVALID_ARG); + retval = -1; + goto cleanup; + } + + /* See if string matches friendly names */ + retval = sunw_find_fname(name_str, work_kl, work_cl, + pkey, cert); + if (retval != 0) { + if (retval == -1) + goto cleanup; + else + goto last_part; + } + } + + if (matchty & DO_FIRST_PAIR) { + + /* Find the first cert and private key and return them */ + retval = get_key_cert(0, work_kl, pkey, work_cl, cert); + if (retval != 0) { + if (retval == -1) + goto cleanup; + else + goto last_part; + } + } + + if (matchty & DO_LAST_PAIR) { + + /* + * Find the last matching cert and private key and return + * them. Since keys which don't have matching client certs + * are at the end of the list of keys, use the number of + * client certs to compute the position of the last private + * key which matches a client cert. + */ + n = sk_X509_num(work_cl) - 1; + retval = get_key_cert(n, work_kl, pkey, work_cl, cert); + if (retval != 0) { + if (retval == -1) + goto cleanup; + else + goto last_part; + } + } + + if (matchty & DO_UNMATCHING) { + STACK_OF(EVP_PKEY) *tmpk; + STACK_OF(X509) *tmpc; + + /* Find the first cert and private key and return them */ + tmpc = work_cl; + if (work_cl == NULL || sk_X509_num(work_cl) == 0) + tmpc = work_ca; + tmpk = work_kl; + if (work_kl == NULL || sk_EVP_PKEY_num(work_kl) == 0) + tmpk = nocerts; + retval = get_key_cert(0, tmpk, pkey, tmpc, cert); + if (retval != 0) { + if (retval == -1) + goto cleanup; + else + goto last_part; + } + } + +last_part: + /* If no errors, terminate normally */ + if (retval != -1) + retval |= set_results(NULL, NULL, NULL, NULL, ca, &work_ca, + NULL, NULL); + if (retval >= 0) { + goto clean_part; + } + + /* Fallthrough is intentional in error cases. */ +cleanup: + if (pkey != NULL && *pkey != NULL) { + sunw_evp_pkey_free(*pkey); + *pkey = NULL; + } + if (cert != NULL && *cert != NULL) { + X509_free(*cert); + *cert = NULL; + } + +clean_part: + + if (work_kl != NULL) { + sk_EVP_PKEY_pop_free(work_kl, sunw_evp_pkey_free); + } + if (work_ca != NULL) + sk_X509_pop_free(work_ca, X509_free); + if (work_cl != NULL) + sk_X509_pop_free(work_cl, X509_free); + + return (retval); +} + +/* + * parse_outer - Unpack the outer PKCS#12 structure and go through the + * individual bags. Return stacks of certs, private keys found and + * CA certs found. + * + * Note about error codes: This function is an internal function, and the + * place where it is called sets error codes. + * + * Returns: + * 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * 1 - PKCS12 data object was parsed and lists of certs and private keys + * were returned. + */ +static int +parse_outer(PKCS12 *p12, const char *pass, STACK_OF(EVP_PKEY) *kl, + STACK_OF(X509) *cl) +{ + STACK_OF(PKCS12_SAFEBAG) *bags; + STACK_OF(PKCS7) *asafes; + int i, bagnid; + PKCS7 *p7; + + if ((asafes = M_PKCS12_unpack_authsafes(p12)) == NULL) + return (0); + + for (i = 0; i < sk_PKCS7_num(asafes); i++) { + p7 = sk_PKCS7_value(asafes, i); + bagnid = OBJ_obj2nid(p7->type); + if (bagnid == NID_pkcs7_data) { + bags = M_PKCS12_unpack_p7data(p7); + } else if (bagnid == NID_pkcs7_encrypted) { + /* + * A length of '-1' means strlen() can be used + * to determine the password length. + */ + bags = M_PKCS12_unpack_p7encdata(p7, pass, -1); + } else { + SUNWerr(SUNW_F_PARSE_OUTER, SUNW_R_BAD_BAGTYPE); + return (0); + } + + if (bags == NULL) { + SUNWerr(SUNW_F_PARSE_OUTER, SUNW_R_PARSE_BAG_ERR); + sk_PKCS7_pop_free(asafes, PKCS7_free); + return (0); + } + if (parse_all_bags(bags, pass, kl, cl) == 0) { + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + sk_PKCS7_pop_free(asafes, PKCS7_free); + return (0); + } + } + + return (1); +} + +/* + * parse_all_bags - go through the stack of bags, parsing each. + * + * Note about error codes: This function is an internal function, and the + * place where it is called sets error codes. + * + * Returns: + * 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * 1 - Stack of safebags was parsed and lists of certs and private keys + * were returned. + */ +static int +parse_all_bags(STACK_OF(PKCS12_SAFEBAG) *bags, const char *pass, + STACK_OF(EVP_PKEY) *kl, STACK_OF(X509) *cl) +{ + int i; + for (i = 0; i < sk_PKCS12_SAFEBAG_num(bags); i++) { + if (parse_one_bag(sk_PKCS12_SAFEBAG_value(bags, i), + pass, kl, cl) == 0) + return (0); + } + return (1); +} + +/* + * parse_one_bag - Parse an individual bag + * + * i = parse_one_bag(bag, pass, kl, cl); + * + * Arguments: + * bag - pkcs12 safebag to parse. + * pass - password for use in decryption of shrouded keybag + * kl - Stack of private keys found so far. New private keys will + * be added here if found. + * cl - Stack of certs found so far. New certificates will be + * added here if found. + * + * Returns: + * 0 - An error returned. Call ERR_get_error() to get errors information. + * Where possible, memory has been freed. + * 1 - one safebag was parsed. If it contained a cert or private key, it + * was added to the stack of certs or private keys found, respectively. + * localKeyId or friendlyName attributes are returned with the + * private key or certificate. + */ +static int +parse_one_bag(PKCS12_SAFEBAG *bag, const char *pass, STACK_OF(EVP_PKEY) *kl, + STACK_OF(X509) *cl) +{ + X509_ATTRIBUTE *attr = NULL; + ASN1_TYPE *keyid = NULL; + ASN1_TYPE *fname = NULL; + PKCS8_PRIV_KEY_INFO *p8; + EVP_PKEY *pkey = NULL; + X509 *x509 = NULL; + uchar_t *data = NULL; + char *str = NULL; + int retval = 1; + + keyid = PKCS12_get_attr(bag, NID_localKeyID); + fname = PKCS12_get_attr(bag, NID_friendlyName); + + switch (M_PKCS12_bag_type(bag)) { + case NID_keyBag: + if ((pkey = EVP_PKCS82PKEY(bag->value.keybag)) == NULL) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, SUNW_R_PARSE_BAG_ERR); + retval = 0; + break; + } + break; + + case NID_pkcs8ShroudedKeyBag: + /* + * A length of '-1' means strlen() can be used + * to determine the password length. + */ + if ((p8 = M_PKCS12_decrypt_skey(bag, pass, -1)) == NULL) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, SUNW_R_PARSE_BAG_ERR); + retval = 0; + break; + } + pkey = EVP_PKCS82PKEY(p8); + PKCS8_PRIV_KEY_INFO_free(p8); + if (pkey == NULL) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, SUNW_R_PARSE_BAG_ERR); + retval = 0; + } + break; + + case NID_certBag: + if (M_PKCS12_cert_bag_type(bag) != NID_x509Certificate) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, SUNW_R_BAD_CERTTYPE); + break; + } + if ((x509 = M_PKCS12_certbag2x509(bag)) == NULL) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_PARSE_CERT_ERR); + retval = 0; + break; + } + + if (keyid != NULL) { + if (keyid->type != V_ASN1_OCTET_STRING) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_BAD_LKID); + retval = 0; + break; + } + if (X509_keyid_set1(x509, + keyid->value.octet_string->data, + keyid->value.octet_string->length) == 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_SET_LKID_ERR); + retval = 0; + break; + } + } + + if (fname != NULL) { + ASN1_STRING *tmpstr = NULL; + int len; + + if (fname->type != V_ASN1_BMPSTRING) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_BAD_FNAME); + retval = 0; + break; + } + + tmpstr = fname->value.asn1_string; + len = ASN1_STRING_to_UTF8(&data, tmpstr); + if (len < 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_SET_FNAME_ERR); + retval = 0; + break; + } + + if (X509_alias_set1(x509, data, len) == 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_SET_FNAME_ERR); + retval = 0; + break; + } + } + + if (sk_X509_push(cl, x509) == 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, SUNW_R_MEMORY_FAILURE); + retval = 0; + break; + } + x509 = NULL; + break; + + case NID_safeContentsBag: + if (keyid != NULL) + ASN1_TYPE_free(keyid); + if (fname != NULL) + ASN1_TYPE_free(fname); + if (parse_all_bags(bag->value.safes, pass, kl, cl) == 0) { + /* + * Error already on stack + */ + return (0); + } + return (1); + + default: + if (keyid != NULL) + ASN1_TYPE_free(keyid); + if (fname != NULL) + ASN1_TYPE_free(fname); + SUNWerr(SUNW_F_PARSE_ONE_BAG, SUNW_R_BAD_BAGTYPE); + return (0); + } + + + if (pkey != NULL) { + if (retval != 0 && (keyid != NULL || fname != NULL) && + pkey->attributes == NULL) { + pkey->attributes = sk_X509_ATTRIBUTE_new_null(); + if (pkey->attributes == NULL) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_MEMORY_FAILURE); + retval = 0; + } + } + + if (retval != 0 && keyid != NULL) { + attr = type2attrib(keyid, NID_localKeyID); + if (attr == NULL) + /* + * Error already on stack + */ + retval = 0; + else { + keyid = NULL; + if (sk_X509_ATTRIBUTE_push(pkey->attributes, + attr) == 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_MEMORY_FAILURE); + retval = 0; + } else { + attr = NULL; + } + } + } + + if (retval != 0 && fname != NULL) { + attr = type2attrib(fname, NID_friendlyName); + if (attr == NULL) { + /* + * Error already on stack + */ + retval = 0; + } else { + fname = NULL; + if (sk_X509_ATTRIBUTE_push(pkey->attributes, + attr) == 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_MEMORY_FAILURE); + retval = 0; + } else { + attr = NULL; + } + } + } + + /* Save the private key */ + if (retval != 0) { + if (sk_EVP_PKEY_push(kl, pkey) == 0) { + SUNWerr(SUNW_F_PARSE_ONE_BAG, + SUNW_R_MEMORY_FAILURE); + retval = 0; + } else { + pkey = NULL; + } + } + } + + if (pkey != NULL) { + sunw_evp_pkey_free(pkey); + } + + if (x509 != NULL) + X509_free(x509); + + if (keyid != NULL) + ASN1_TYPE_free(keyid); + + if (fname != NULL) + ASN1_TYPE_free(fname); + + if (attr != NULL) + X509_ATTRIBUTE_free(attr); + + if (data != NULL) + OPENSSL_free(data); + + if (str != NULL) + OPENSSL_free(str); + + return (retval); +} + +/* + * This function uses the only function that reads PEM files, regardless of + * the kinds of information included (private keys, public keys, cert requests, + * certs). Other interfaces that read files require that the application + * specifically know what kinds of things to read next, and call different + * interfaces for the different kinds of entities. + * + * There is only one aspect of this function that's a bit problematic. + * If it finds an encrypted private key, it does not decrypt it. It returns + * the encrypted data and other information needed to decrypt it. The caller + * must do the decryption. This function does the decoding. + */ +static int +pem_info(FILE *fp, pem_password_cb cb, void *userdata, + STACK_OF(EVP_PKEY) **pkeys, STACK_OF(X509) **certs) +{ + STACK_OF(X509_INFO) *info; + STACK_OF(EVP_PKEY) *work_kl; + STACK_OF(X509) *work_cl; + X509_INFO *x; + int retval = 0; + int i; + + info = PEM_X509_INFO_read(fp, NULL, cb, userdata); + if (info == NULL) { + SUNWerr(SUNW_F_PEM_INFO, SUNW_R_READ_ERR); + return (-1); + } + + /* + * Allocate the working stacks for private key(s) and for the cert(s). + */ + if ((work_kl = sk_EVP_PKEY_new_null()) == NULL) { + SUNWerr(SUNW_F_PEM_INFO, SUNW_R_MEMORY_FAILURE); + retval = -1; + goto cleanup; + } + + if ((work_cl = sk_X509_new_null()) == NULL) { + SUNWerr(SUNW_F_PEM_INFO, SUNW_R_MEMORY_FAILURE); + retval = -1; + goto cleanup; + } + + /* + * Go through the entries in the info structure. + */ + for (i = 0; i < sk_X509_INFO_num(info); i++) { + x = sk_X509_INFO_value(info, i); + if (x->x509) { + if (sk_X509_push(work_cl, x->x509) == 0) { + retval = -1; + break; + } + x->x509 = NULL; + } + if (x->x_pkey != NULL && x->x_pkey->dec_pkey != NULL && + (x->x_pkey->dec_pkey->type == EVP_PKEY_RSA || + x->x_pkey->dec_pkey->type == EVP_PKEY_DSA)) { + const uchar_t *p; + + /* + * If the key was encrypted, PEM_X509_INFO_read does + * not decrypt it. If that is the case, the 'enc_pkey' + * field is set to point to the unencrypted key data. + * Go through the additional steps to decode it before + * going on. + */ + if (x->x_pkey->enc_pkey != NULL) { + + if (PEM_do_header(&x->enc_cipher, + (uchar_t *)x->enc_data, + (long *)&x->enc_len, + cb, userdata) == 0) { + if (ERR_GET_REASON(ERR_peek_error()) == + PEM_R_BAD_PASSWORD_READ) { + SUNWerr(SUNW_F_PEM_INFO, + SUNW_R_PASSWORD_ERR); + } else { + SUNWerr(SUNW_F_PEM_INFO, + SUNW_R_PKEY_READ_ERR); + } + retval = -1; + break; + } + if (x->x_pkey->dec_pkey->type == EVP_PKEY_RSA) { + RSA **pp; + + pp = &(x->x_pkey->dec_pkey->pkey.rsa); + p = (uchar_t *)x->enc_data; + if (d2i_RSAPrivateKey(pp, &p, + x->enc_len) == NULL) { + SUNWerr(SUNW_F_PEM_INFO, + SUNW_R_PKEY_READ_ERR); + retval = -1; + break; + } + } else { + DSA **pp; + + pp = &(x->x_pkey->dec_pkey->pkey.dsa); + p = (uchar_t *)x->enc_data; + if (d2i_DSAPrivateKey(pp, &p, + x->enc_len) == NULL) { + SUNWerr(SUNW_F_PEM_INFO, + SUNW_R_PKEY_READ_ERR); + retval = -1; + break; + } + } + } + + /* Save the key. */ + retval = sk_EVP_PKEY_push(work_kl, x->x_pkey->dec_pkey); + if (retval == 0) { + retval = -1; + break; + } + x->x_pkey->dec_pkey = NULL; + } else if (x->x_pkey != NULL) { + SUNWerr(SUNW_F_PEM_INFO, SUNW_R_BAD_PKEYTYPE); + retval = -1; + break; + } + } + if (retval == -1) + goto cleanup; + + /* If error occurs, then error already on stack */ + retval = set_results(pkeys, &work_kl, certs, &work_cl, NULL, NULL, + NULL, NULL); + +cleanup: + if (work_kl != NULL) { + sk_EVP_PKEY_pop_free(work_kl, sunw_evp_pkey_free); + } + if (work_cl != NULL) + sk_X509_pop_free(work_cl, X509_free); + + sk_X509_INFO_pop_free(info, X509_INFO_free); + + return (retval); +} + +/* + * sunw_append_keys - Given two stacks of private keys, remove the keys from + * the second stack and append them to the first. Both stacks must exist + * at time of call. + * + * Arguments: + * dst - the stack to receive the keys from 'src' + * src - the stack whose keys are to be moved. + * + * Returns: + * -1 - An error occurred. The error status is set. + * >= 0 - The number of keys that were copied. + */ +static int +sunw_append_keys(STACK_OF(EVP_PKEY) *dst, STACK_OF(EVP_PKEY) *src) +{ + EVP_PKEY *tmpk; + int count = 0; + + while (sk_EVP_PKEY_num(src) > 0) { + tmpk = sk_EVP_PKEY_delete(src, 0); + if (sk_EVP_PKEY_push(dst, tmpk) == 0) { + sunw_evp_pkey_free(tmpk); + SUNWerr(SUNW_F_APPEND_KEYS, SUNW_R_MEMORY_FAILURE); + return (-1); + } + count ++; + } + + return (count); +} + +/* + * move_certs - Given two stacks of certs, remove the certs from + * the second stack and append them to the first. + * + * Arguments: + * dst - the stack to receive the certs from 'src' + * src - the stack whose certs are to be moved. + * + * Returns: + * -1 - An error occurred. The error status is set. + * >= 0 - The number of certs that were copied. + */ +static int +move_certs(STACK_OF(X509) *dst, STACK_OF(X509) *src) +{ + X509 *tmpc; + int count = 0; + + while (sk_X509_num(src) > 0) { + tmpc = sk_X509_delete(src, 0); + if (sk_X509_push(dst, tmpc) == 0) { + X509_free(tmpc); + SUNWerr(SUNW_F_MOVE_CERTS, SUNW_R_MEMORY_FAILURE); + return (-1); + } + count++; + } + + return (count); +} + +/* + * get_key_cert - Get a cert and its matching key from the stacks of certs + * and keys. They are removed from the stacks. + * + * Arguments: + * n - Offset of the entries to return. + * kl - Points to a stack of private keys that matches the list of + * certs below. + * pkey - Points at location where the address of the matching private + * key will be stored. + * cl - Points to a stack of client certs with matching private keys. + * cert - Points to locaiton where the address of the matching client cert + * will be returned + * + * The assumption is that the stacks of keys and certs contain key/cert pairs, + * with entries in the same order and hence at the same offset. Provided + * the key and cert selected match, each will be removed from its stack and + * returned. + * + * A stack of certs can be passed in without a stack of private keys, and vise + * versa. In that case, the indicated key/cert will be returned. + * + * Returns: + * 0 - No matches were found. + * > 0 - Bits set based on FOUND_* definitions, indicating what is returned. + * This can be FOUND_PKEY, FOUND_CERT or (FOUND_PKEY | FOUND_CERT). + */ +static int +get_key_cert(int n, STACK_OF(EVP_PKEY) *kl, EVP_PKEY **pkey, STACK_OF(X509) *cl, + X509 **cert) +{ + int retval = 0; + int nk; + int nc; + + nk = (kl != NULL) ? sk_EVP_PKEY_num(kl) : 0; + nc = (cl != NULL) ? sk_X509_num(cl) : 0; + + if (pkey != NULL && *pkey == NULL) { + if (nk > 0 && n >= 0 || n < nk) { + *pkey = sk_EVP_PKEY_delete(kl, n); + if (*pkey != NULL) + retval |= FOUND_PKEY; + } + } + + if (cert != NULL && *cert == NULL) { + if (nc > 0 && n >= 0 && n < nc) { + *cert = sk_X509_delete(cl, n); + if (*cert != NULL) + retval |= FOUND_CERT; + } + } + + return (retval); +} + + +/* + * asc2bmpstring - Convert a regular C ASCII string to an ASn1_STRING in + * ASN1_BMPSTRING format. + * + * Arguments: + * str - String to be convered. + * len - Length of the string. + * + * Returns: + * == NULL - An error occurred. Error information (accessible by + * ERR_get_error()) is set. + * != NULL - Points to an ASN1_BMPSTRING structure with the converted + * string as a value. + */ +static ASN1_BMPSTRING * +asc2bmpstring(const char *str, int len) +{ + ASN1_BMPSTRING *bmp = NULL; + uchar_t *uni = NULL; + int unilen; + + /* Convert the character to the bmp format. */ + if (asc2uni(str, len, &uni, &unilen) == 0) { + SUNWerr(SUNW_F_ASC2BMPSTRING, SUNW_R_MEMORY_FAILURE); + return (NULL); + } + + /* + * Adjust for possible pair of NULL bytes at the end because + * asc2uni() returns a doubly null terminated string. + */ + if (uni[unilen - 1] == '\0' && uni[unilen - 2] == '\0') + unilen -= 2; + + /* Construct comparison string with correct format */ + bmp = M_ASN1_BMPSTRING_new(); + if (bmp == NULL) { + SUNWerr(SUNW_F_ASC2BMPSTRING, SUNW_R_MEMORY_FAILURE); + OPENSSL_free(uni); + return (NULL); + } + + bmp->data = uni; + bmp->length = unilen; + + return (bmp); +} + +/* + * utf82ascstr - Convert a UTF8STRING string to a regular C ASCII string. + * This goes through an intermediate step with a ASN1_STRING type of + * IA5STRING (International Alphabet 5, which is the same as ASCII). + * + * Arguments: + * str - UTF8STRING to be converted. + * + * Returns: + * == NULL - An error occurred. Error information (accessible by + * ERR_get_error()) is set. + * != NULL - Points to a NULL-termianted ASCII string. The caller must + * free it. + */ +static uchar_t * +utf82ascstr(ASN1_UTF8STRING *ustr) +{ + ASN1_STRING tmpstr; + ASN1_STRING *astr = &tmpstr; + uchar_t *retstr = NULL; + int mbflag; + int ret; + + if (ustr == NULL || ustr->type != V_ASN1_UTF8STRING) { + SUNWerr(SUNW_F_UTF82ASCSTR, SUNW_R_INVALID_ARG); + return (NULL); + } + + mbflag = MBSTRING_ASC; + tmpstr.data = NULL; + tmpstr.length = 0; + + ret = ASN1_mbstring_copy(&astr, ustr->data, ustr->length, mbflag, + B_ASN1_IA5STRING); + if (ret < 0) { + SUNWerr(SUNW_F_UTF82ASCSTR, SUNW_R_STR_CONVERT_ERR); + return (NULL); + } + + retstr = OPENSSL_malloc(astr->length + 1); + if (retstr == NULL) { + SUNWerr(SUNW_F_UTF82ASCSTR, SUNW_R_MEMORY_FAILURE); + return (NULL); + } + + (void) memcpy(retstr, astr->data, astr->length); + retstr[astr->length] = '\0'; + OPENSSL_free(astr->data); + + return (retstr); +} + + +/* + * type2attrib - Given a ASN1_TYPE, return a X509_ATTRIBUTE of the type + * specified by the given NID. + * + * Arguments: + * ty - Type structure to be made into an attribute + * nid - NID of the attribute + * + * Returns: + * NULL An error occurred. + * != NULL An X509_ATTRIBUTE structure. + */ +X509_ATTRIBUTE * +type2attrib(ASN1_TYPE *ty, int nid) +{ + X509_ATTRIBUTE *a; + + if ((a = X509_ATTRIBUTE_new()) == NULL || + (a->value.set = sk_ASN1_TYPE_new_null()) == NULL || + sk_ASN1_TYPE_push(a->value.set, ty) == 0) { + if (a != NULL) + X509_ATTRIBUTE_free(a); + SUNWerr(SUNW_F_TYPE2ATTRIB, SUNW_R_MEMORY_FAILURE); + return (NULL); + } + a->single = 0; + a->object = OBJ_nid2obj(nid); + + return (a); +} + +/* + * attrib2type - Given a X509_ATTRIBUTE, return pointer to the ASN1_TYPE + * component + * + * Arguments: + * attr - Attribute structure containing a type. + * + * Returns: + * NULL An error occurred. + * != NULL An ASN1_TYPE structure. + */ +static ASN1_TYPE * +attrib2type(X509_ATTRIBUTE *attr) +{ + ASN1_TYPE *ty = NULL; + + if (attr == NULL || attr->single == 1) + return (NULL); + + if (sk_ASN1_TYPE_num(attr->value.set) > 0) + ty = sk_ASN1_TYPE_value(attr->value.set, 0); + + return (ty); +} + +/* + * find_attr_by_nid - Given a ASN1_TYPE, return the offset of a X509_ATTRIBUTE + * of the type specified by the given NID. + * + * Arguments: + * attrs - Stack of attributes to search + * nid - NID of the attribute being searched for + * + * Returns: + * -1 None found + * != -1 Offset of the matching attribute. + */ +static int +find_attr_by_nid(STACK_OF(X509_ATTRIBUTE) *attrs, int nid) +{ + X509_ATTRIBUTE *a; + int i; + + if (attrs == NULL) + return (-1); + + for (i = 0; i < sk_X509_ATTRIBUTE_num(attrs); i++) { + a = sk_X509_ATTRIBUTE_value(attrs, i); + if (OBJ_obj2nid(a->object) == nid) + return (i); + } + return (-1); +} + +/* + * Called by our PKCS12 code to read our function and error codes + * into memory so that the OpenSSL framework can retrieve them. + */ +void +ERR_load_SUNW_strings(void) +{ + assert(SUNW_lib_error_code == 0); +#ifndef OPENSSL_NO_ERR + /* + * Have OpenSSL provide us with a unique ID. + */ + SUNW_lib_error_code = ERR_get_next_error_library(); + + ERR_load_strings(SUNW_lib_error_code, SUNW_str_functs); + ERR_load_strings(SUNW_lib_error_code, SUNW_str_reasons); + + SUNW_lib_name->error = ERR_PACK(SUNW_lib_error_code, 0, 0); + ERR_load_strings(0, SUNW_lib_name); +#endif +} + +/* + * The SUNWerr macro resolves to this routine. So when we need + * to push an error, this routine does it for us. Notice that + * the SUNWerr macro provides a filename and line #. + */ +void +ERR_SUNW_error(int function, int reason, char *file, int line) +{ + assert(SUNW_lib_error_code != 0); +#ifndef OPENSSL_NO_ERR + ERR_PUT_error(SUNW_lib_error_code, function, reason, file, line); +#endif +} + +/* + * check_time - Given an indication of the which time(s) to check, check + * that time or those times against the current time and return the + * relationship. + * + * Arguments: + * chkwhat - What kind of check to do. + * cert - The cert to check. + * + * Returns: + * CHKERR_* values. + */ +static chk_errs_t +check_time(chk_actions_t chkwhat, X509 *cert) +{ + int i; + + if (chkwhat == CHK_NOT_BEFORE || chkwhat == CHK_BOTH) { + i = X509_cmp_time(X509_get_notBefore(cert), NULL); + if (i == 0) + return (CHKERR_TIME_BEFORE_BAD); + if (i > 0) + return (CHKERR_TIME_IS_BEFORE); + + /* The current time is after the 'not before' time */ + } + + if (chkwhat == CHK_NOT_AFTER || chkwhat == CHK_BOTH) { + i = X509_cmp_time(X509_get_notAfter(cert), NULL); + if (i == 0) + return (CHKERR_TIME_AFTER_BAD); + if (i < 0) + return (CHKERR_TIME_HAS_EXPIRED); + } + + return (CHKERR_TIME_OK); +} + +/* + * find_attr - Look for a given attribute of the type associated with the NID. + * + * Arguments: + * nid - NID for the attribute to be found (either NID_friendlyName or + * NID_locakKeyId) + * str - ASN1_STRING-type structure containing the value to be found, + * FriendlyName expects a ASN1_BMPSTRING and localKeyID uses a + * ASN1_STRING. + * kl - Points to a stack of private keys. + * pkey - Points at a location where the address of the matching private + * key will be stored. + * cl - Points to a stack of client certs with matching private keys. + * cert - Points to locaiton where the address of the matching client cert + * will be returned + * + * This function is designed to process lists of certs and private keys. + * This is made complex because these the attributes are stored differently + * for certs and for keys. For certs, only a few attributes are retained. + * FriendlyName is stored in the aux structure, under the name 'alias'. + * LocalKeyId is also stored in the aux structure, under the name 'keyid'. + * A pkey structure has a stack of attributes. + * + * The basic approach is: + * - If there there is no stack of certs but a stack of private keys exists, + * search the stack of keys for a match. Alternately, if there is a stack + * of certs and no private keys, search the certs. + * + * - If there are both certs and keys, assume that the matching certs and + * keys are in their respective stacks, with matching entries in the same + * order. Search for the name or keyid in the stack of certs. If it is + * not found, then this function returns 0 (nothing found). + * + * - Once a cert is found, verify that the key actually matches by + * comparing the private key with the public key (in the cert). + * If they don't match, return an error. + * + * A pointer to cert and/or pkey which matches the name or keyid is stored + * in the return arguments. + * + * Returns: + * 0 - No matches were found. + * > 0 - Bits set based on FOUND_* definitions, indicating what was found. + * This can be FOUND_PKEY, FOUND_CERT or (FOUND_PKEY | FOUND_CERT). + */ +static int +find_attr(int nid, ASN1_STRING *str, STACK_OF(EVP_PKEY) *kl, EVP_PKEY **pkey, + STACK_OF(X509) *cl, X509 **cert) +{ + ASN1_UTF8STRING *ustr = NULL; + ASN1_STRING *s; + ASN1_TYPE *t; + EVP_PKEY *p; + uchar_t *fname = NULL; + X509 *x; + int found = 0; + int chkcerts; + int len; + int res; + int c = -1; + int k = -1; + + chkcerts = (cert != NULL || pkey != NULL) && cl != NULL; + if (chkcerts && nid == NID_friendlyName && + str->type == V_ASN1_BMPSTRING) { + ustr = ASN1_UTF8STRING_new(); + if (ustr == NULL) { + SUNWerr(SUNW_F_FINDATTR, SUNW_R_MEMORY_FAILURE); + return (0); + } + len = ASN1_STRING_to_UTF8(&fname, str); + if (fname == NULL) { + ASN1_UTF8STRING_free(ustr); + SUNWerr(SUNW_F_FINDATTR, SUNW_R_STR_CONVERT_ERR); + return (0); + } + + if (ASN1_STRING_set(ustr, fname, len) == 0) { + ASN1_UTF8STRING_free(ustr); + OPENSSL_free(fname); + SUNWerr(SUNW_F_FINDATTR, SUNW_R_MEMORY_FAILURE); + return (0); + } + } + + if (chkcerts) { + for (c = 0; c < sk_X509_num(cl); c++) { + res = -1; + x = sk_X509_value(cl, c); + if (nid == NID_friendlyName && ustr != NULL) { + if (x->aux == NULL || x->aux->alias == NULL) + continue; + s = x->aux->alias; + if (s != NULL && s->type == ustr->type && + s->data != NULL) { + res = ASN1_STRING_cmp(s, ustr); + } + } else { + if (x->aux == NULL || x->aux->keyid == NULL) + continue; + s = x->aux->keyid; + if (s != NULL && s->type == str->type && + s->data != NULL) { + res = ASN1_STRING_cmp(s, str); + } + } + if (res == 0) { + if (cert != NULL) + *cert = sk_X509_delete(cl, c); + found = FOUND_CERT; + break; + } + } + if (ustr != NULL) { + ASN1_UTF8STRING_free(ustr); + OPENSSL_free(fname); + } + } + + if (pkey != NULL && kl != NULL) { + /* + * Looking for pkey to match a cert? If so, assume that + * lists of certs and their matching pkeys are in the same + * order. Call X509_check_private_key() to verify this + * assumption. + */ + if (found != 0 && cert != NULL) { + k = c; + p = sk_EVP_PKEY_value(kl, k); + if (X509_check_private_key(x, p) != 0) { + if (pkey != NULL) + *pkey = sk_EVP_PKEY_delete(kl, k); + found |= FOUND_PKEY; + } + } else if (cert == NULL) { + for (k = 0; k < sk_EVP_PKEY_num(kl); k++) { + p = sk_EVP_PKEY_value(kl, k); + if (p == NULL || p->attributes == NULL) + continue; + + t = PKCS12_get_attr_gen(p->attributes, nid); + if (t != NULL || ASN1_STRING_cmp(str, + t->value.asn1_string) == 0) + continue; + + found |= FOUND_PKEY; + if (pkey != NULL) + *pkey = sk_EVP_PKEY_delete(kl, k); + break; + } + } + } + + return (found); +} + +/* + * set_results - Given two pointers to stacks of private keys, certs or CA + * CA certs, either copy the second stack to the first, or append the + * contents of the second to the first. + * + * Arguments: + * pkeys - Points to stack of pkeys + * work_kl - Points to working stack of pkeys + * certs - Points to stack of certs + * work_cl - Points to working stack of certs + * cacerts - Points to stack of CA certs + * work_ca - Points to working stack of CA certs + * xtrakeys - Points to stack of unmatcned pkeys + * work_xl - Points to working stack of unmatcned pkeys + * + * The arguments are in pairs. The first of each pair points to a stack + * of keys or certs. The second of the pair points at a 'working stack' + * of the same type of entities. Actions taken are as follows: + * + * - If either the first or second argument is NULL, or if there are no + * members in the second stack, there is nothing to do. + * - If the first argument points to a pointer which is NULL, then there + * is no existing stack for the first argument. Copy the stack pointer + * from the second argument to the first argument and NULL out the stack + * pointer for the second. + * - Otherwise, go through the elements of the second stack, removing each + * and adding it to the first stack. + * + * Returns: + * == -1 - An error occurred. Call ERR_get_error() to get error information. + * == 0 - No matching returns were found. + * > 0 - This is the arithmetic 'or' of the FOUND_* bits that indicate which + * of the requested entries were manipulated. + */ +static int +set_results(STACK_OF(EVP_PKEY) **pkeys, STACK_OF(EVP_PKEY) **work_kl, + STACK_OF(X509) **certs, STACK_OF(X509) **work_cl, + STACK_OF(X509) **cacerts, STACK_OF(X509) **work_ca, + STACK_OF(EVP_PKEY) **xtrakeys, STACK_OF(EVP_PKEY) **work_xl) +{ + int retval = 0; + + if (pkeys != NULL && work_kl != NULL && *work_kl != NULL && + sk_EVP_PKEY_num(*work_kl) > 0) { + if (*pkeys == NULL) { + *pkeys = *work_kl; + *work_kl = NULL; + } else { + if (sunw_append_keys(*pkeys, *work_kl) < 0) { + return (-1); + } + } + retval |= FOUND_PKEY; + } + if (certs != NULL && work_cl != NULL && *work_cl != NULL && + sk_X509_num(*work_cl) > 0) { + if (*certs == NULL) { + *certs = *work_cl; + *work_cl = NULL; + } else { + if (move_certs(*certs, *work_cl) < 0) { + return (-1); + } + } + retval |= FOUND_CERT; + } + + if (cacerts != NULL && work_ca != NULL && *work_ca != NULL && + sk_X509_num(*work_ca) > 0) { + if (*cacerts == NULL) { + *cacerts = *work_ca; + *work_ca = NULL; + } else { + if (move_certs(*cacerts, *work_ca) < 0) { + return (-1); + } + } + retval |= FOUND_CA_CERTS; + } + + if (xtrakeys != NULL && work_xl != NULL && *work_xl != NULL && + sk_EVP_PKEY_num(*work_xl) > 0) { + if (*xtrakeys == NULL) { + *xtrakeys = *work_xl; + *work_xl = NULL; + } else { + if (sunw_append_keys(*xtrakeys, *work_xl) < 0) { + return (-1); + } + } + retval |= FOUND_XPKEY; + } + + return (retval); +} diff --git a/usr/src/lib/libpkg/common/p12lib.h b/usr/src/lib/libpkg/common/p12lib.h new file mode 100644 index 0000000000..3d80ddaa35 --- /dev/null +++ b/usr/src/lib/libpkg/common/p12lib.h @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * Copyright (c) 1999 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _P12LIB_H +#define _P12LIB_H + + +#include <openssl/pkcs12.h> +#include <openssl/pem.h> + +/* + * PKCS12 file routines borrowed from SNT's libwanboot. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* These declarations allow us to make stacks of EVP_PKEY objects */ +DECLARE_STACK_OF(EVP_PKEY) +#define sk_EVP_PKEY_new_null() SKM_sk_new_null(EVP_PKEY) +#define sk_EVP_PKEY_free(st) SKM_sk_free(EVP_PKEY, (st)) +#define sk_EVP_PKEY_num(st) SKM_sk_num(EVP_PKEY, (st)) +#define sk_EVP_PKEY_value(st, i) SKM_sk_value(EVP_PKEY, (st), (i)) +#define sk_EVP_PKEY_push(st, val) SKM_sk_push(EVP_PKEY, (st), (val)) +#define sk_EVP_PKEY_find(st, val) SKM_sk_find(EVP_PKEY, (st), (val)) +#define sk_EVP_PKEY_delete(st, i) SKM_sk_delete(EVP_PKEY, (st), (i)) +#define sk_EVP_PKEY_delete_ptr(st, ptr) SKM_sk_delete_ptr(EVP_PKEY, (st), (ptr)) +#define sk_EVP_PKEY_insert(st, val, i) SKM_sk_insert(EVP_PKEY, (st), (val), (i)) +#define sk_EVP_PKEY_pop_free(st, free_func) SKM_sk_pop_free(EVP_PKEY, (st), \ + (free_func)) +#define sk_EVP_PKEY_pop(st) SKM_sk_pop(EVP_PKEY, (st)) + +/* Error reporting routines required by OpenSSL */ +#define SUNW_LIB_NAME "SUNW_PKCS12" +#define SUNWerr(f, r) ERR_SUNW_error((f), (r), __FILE__, __LINE__) + +/* Error codes for the SUNW functions. */ +/* OpenSSL prefers codes to start at 100 */ + +/* Function codes. */ +typedef enum { + SUNW_F_USE_X509CERT = 100, + SUNW_F_USE_PKEY, + SUNW_F_USE_TASTORE, + SUNW_F_USE_CERTFILE, + SUNW_F_USE_KEYFILE, + SUNW_F_USE_TRUSTFILE, + SUNW_F_READ_FILE, + SUNW_F_DOPARSE, + SUNW_F_PKCS12_PARSE, + SUNW_F_PKCS12_CONTENTS, + SUNW_F_PARSE_ONE_BAG, + SUNW_F_PKCS12_CREATE, + SUNW_F_SPLIT_CERTS, + SUNW_F_FIND_LOCALKEYID, + SUNW_F_SET_LOCALKEYID, + SUNW_F_SET_FNAME, + SUNW_F_GET_LOCALKEYID, + SUNW_F_GET_PKEY_FNAME, + SUNW_F_APPEND_KEYS, + SUNW_F_PEM_CONTENTS, + SUNW_F_PEM_INFO, + SUNW_F_ASC2BMPSTRING, + SUNW_F_UTF82ASCSTR, + SUNW_F_FINDATTR, + SUNW_F_TYPE2ATTRIB, + SUNW_F_MOVE_CERTS, + SUNW_F_FIND_FNAME, + SUNW_F_PARSE_OUTER, + SUNW_F_CHECKFILE +} sunw_err_func_t; + +/* Reason codes. */ +typedef enum { + SUNW_R_INVALID_ARG = 100, + SUNW_R_MEMORY_FAILURE, + SUNW_R_MAC_VERIFY_FAILURE, + SUNW_R_MAC_CREATE_FAILURE, + SUNW_R_BAD_FILETYPE, + SUNW_R_BAD_PKEY, + SUNW_R_BAD_PKEYTYPE, + SUNW_R_PKEY_READ_ERR, + SUNW_R_NO_TRUST_ANCHOR, + SUNW_R_READ_TRUST_ERR, + SUNW_R_ADD_TRUST_ERR, + SUNW_R_PKCS12_PARSE_ERR, + SUNW_R_PKCS12_CREATE_ERR, + SUNW_R_PARSE_BAG_ERR, + SUNW_R_MAKE_BAG_ERR, + SUNW_R_BAD_CERTTYPE, + SUNW_R_PARSE_CERT_ERR, + SUNW_R_BAD_LKID, + SUNW_R_SET_LKID_ERR, + SUNW_R_BAD_FNAME, + SUNW_R_SET_FNAME_ERR, + SUNW_R_BAD_TRUST, + SUNW_R_BAD_BAGTYPE, + SUNW_R_CERT_ERR, + SUNW_R_PKEY_ERR, + SUNW_R_READ_ERR, + SUNW_R_ADD_ATTR_ERR, + SUNW_R_STR_CONVERT_ERR, + SUNW_R_PKCS12_EMPTY_ERR, + SUNW_R_PASSWORD_ERR +} sunw_err_reason_t; + +/* + * Type of checking to perform when calling sunw_check_cert_times + */ +typedef enum { + CHK_NOT_BEFORE = 1, /* Check 'not before' date */ + CHK_NOT_AFTER, /* Check 'not after' date */ + CHK_BOTH /* Check both dates */ +} chk_actions_t; + +/* + * Return type for sunw_check_cert_times + */ +typedef enum { + CHKERR_TIME_OK = 0, /* Current time meets requested checks */ + CHKERR_TIME_BEFORE_BAD, /* 'not before' field is invalid */ + CHKERR_TIME_AFTER_BAD, /* 'not after' field is invalid */ + CHKERR_TIME_IS_BEFORE, /* Current time is before 'not before' */ + CHKERR_TIME_HAS_EXPIRED /* Current time is after 'not after' */ +} chk_errs_t; + +/* + * This type indicates what to do with an attribute being returned. + */ +typedef enum { + GETDO_COPY = 1, /* Simply return the value of the attribute */ + GETDO_DEL /* Delete the attribute at the same time. */ +} getdo_actions_t; + +/* + * For sunw_pkcs12_parse, the following are values for bits that indicate + * various types of searches/matching to do. Any of these values can be + * OR'd together. However, the order in which an attempt will be made + * to satisfy them is the order in which they are listed below. The + * exception is DO_NONE. It should not be OR'd with any other value. + */ +#define DO_NONE 0x00 /* Don't even try to match */ +#define DO_FIND_KEYID 0x01 /* 1st cert, key with matching localkeyid */ +#define DO_FIND_FN 0x02 /* 1st cert, key with matching friendlyname */ +#define DO_FIRST_PAIR 0x04 /* Return first matching cert/key pair found */ +#define DO_LAST_PAIR 0x08 /* Return last matching cert/key pair found */ +#define DO_UNMATCHING 0x10 /* Return first cert and/or key */ + +/* Bits returned, which indicate what values were found. */ +#define FOUND_PKEY 0x01 /* Found one or more private key */ +#define FOUND_CERT 0x02 /* Found one or more client certificate */ +#define FOUND_CA_CERTS 0x04 /* Added at least one cert to the CA list */ +#define FOUND_XPKEY 0x08 /* Found at least one private key which does */ + /* not match a certificate in the certs list */ + +/* p12lib.c */ +PKCS12 *sunw_PKCS12_create(const char *, STACK_OF(EVP_PKEY) *, + STACK_OF(X509) *, STACK_OF(X509) *); + +int sunw_split_certs(STACK_OF(EVP_PKEY) *, STACK_OF(X509) *, + STACK_OF(X509) **, STACK_OF(EVP_PKEY) **); + +void sunw_evp_pkey_free(EVP_PKEY *); +int sunw_set_localkeyid(const char *, int, EVP_PKEY *, X509 *); +int sunw_get_pkey_localkeyid(getdo_actions_t, EVP_PKEY *, char **, int *); +int sunw_get_pkey_fname(getdo_actions_t, EVP_PKEY *, char **); +int sunw_find_localkeyid(char *, int, STACK_OF(EVP_PKEY) *, + STACK_OF(X509) *, EVP_PKEY **, X509 **); +int sunw_find_fname(char *, STACK_OF(EVP_PKEY) *, STACK_OF(X509) *, + EVP_PKEY **, X509 **); +int sunw_set_fname(const char *, EVP_PKEY *, X509 *); +int sunw_check_keys(X509 *, EVP_PKEY *); + +chk_errs_t sunw_check_cert_times(chk_actions_t, X509 *); +extern void ERR_SUNW_error(int function, int reason, char *file, int line); +extern void ERR_load_SUNW_strings(void); +int sunw_PKCS12_contents(PKCS12 *, const char *, + STACK_OF(EVP_PKEY) **, STACK_OF(X509) **); +int sunw_get_cert_fname(getdo_actions_t, X509 *, char **); +int sunw_PEM_contents(FILE *, pem_password_cb, void *, + STACK_OF(EVP_PKEY) **, STACK_OF(X509) **); + +#ifdef __cplusplus +} +#endif + +#endif /* _P12LIB_H */ diff --git a/usr/src/lib/libpkg/common/pkgerr.c b/usr/src/lib/libpkg/common/pkgerr.c new file mode 100644 index 0000000000..eb0254055a --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgerr.c @@ -0,0 +1,135 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +/* + * Module: pkgerr.c + * Description: + * Module for handling error messages that come from libpkg libraries. + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <locale.h> +#include <libintl.h> +#include <stdlib.h> +#include <sys/varargs.h> +#include "pkgerr.h" + +/* max length of any formatted error message */ +#define MAX_ERRMSGLEN 1024 + +/* private structures (not visible outside this file) */ +struct _pkg_err_struct { + int nerrs; + char **msgs; + PKG_ERR_CODE *errs; +}; + +/* ---------------------- public functions ----------------------- */ + +PKG_ERR +*pkgerr_new() +{ + PKG_ERR *newerr; + + newerr = (PKG_ERR *)malloc(sizeof (PKG_ERR)); + newerr->nerrs = 0; + newerr->msgs = NULL; + newerr->errs = NULL; + return (newerr); +} + +void +pkgerr_add(PKG_ERR *err, PKG_ERR_CODE code, char *fmt, ...) +{ + char errmsgbuf[1024]; + va_list ap; + + va_start(ap, fmt); + (void) vsnprintf(errmsgbuf, MAX_ERRMSGLEN, fmt, ap); + va_end(ap); + + err->nerrs++; + + err->msgs = (char **)realloc(err->msgs, + err->nerrs * sizeof (char *)); + err->errs = (PKG_ERR_CODE *)realloc(err->errs, + err->nerrs * sizeof (PKG_ERR_CODE)); + err->msgs[err->nerrs - 1] = strdup(errmsgbuf); + err->errs[err->nerrs - 1] = code; +} + +void +pkgerr_clear(PKG_ERR *err) +{ + int i; + + for (i = 0; i < err->nerrs; i++) { + free(err->msgs[i]); + } + + free(err->msgs); + free(err->errs); + err->msgs = NULL; + err->errs = NULL; + err->nerrs = 0; +} + +int +pkgerr_dump(PKG_ERR *err, FILE *fp) +{ + int i; + + for (i = 0; i < err->nerrs; i++) { + (void) fprintf(fp, err->msgs[i]); + } + return (0); +} + +int +pkgerr_num(PKG_ERR *err) +{ + return (err->nerrs); +} + +char +*pkgerr_get(PKG_ERR *err, int pos) +{ + if (pos < 0 || pos > (err->nerrs - 1)) { + return (NULL); + } + + return (err->msgs[pos]); +} + +void +pkgerr_free(PKG_ERR *err) +{ + pkgerr_clear(err); + free(err); +} diff --git a/usr/src/lib/libpkg/common/pkgerr.h b/usr/src/lib/libpkg/common/pkgerr.h new file mode 100644 index 0000000000..10e0e219d9 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgerr.h @@ -0,0 +1,104 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _PKGERR_H +#define _PKGERR_H + + +/* + * Module: pkgerr.h + * Description: + * + * Implements error routines to handle the creation, + * management, and destruction of error objects, which + * hold error messages and codes returned from libpkg + * routines that support the objects defined herein. + */ + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Public Definitions + */ + +typedef enum { + PKGERR_OK = 0, + PKGERR_EXIST, + PKGERR_READ, + PKGERR_CORRUPT, + PKGERR_PARSE, + PKGERR_BADPASS, + PKGERR_BADALIAS, + PKGERR_INTERNAL, + PKGERR_UNSUP, + PKGERR_NOALIAS, + PKGERR_NOALIASMATCH, + PKGERR_MULTIPLE, + PKGERR_INCOMPLETE, + PKGERR_NOPRIVKEY, + PKGERR_NOPUBKEY, + PKGERR_NOCACERT, + PKGERR_NOMEM, + PKGERR_CHAIN, + PKGERR_LOCKED, + PKGERR_WRITE, + PKGERR_UNLOCK, + PKGERR_TIME, + PKGERR_DUPLICATE, + PKGERR_WEB, + PKGERR_VERIFY +} PKG_ERR_CODE; + +/* + * Public Structures + */ + +/* external reference to PKG_ERR object (contents private) */ +typedef PKG_ERR_CODE pkg_err_t; + +typedef struct _pkg_err_struct PKG_ERR; + +/* + * Public Methods + */ + +PKG_ERR *pkgerr_new(); +void pkgerr_add(PKG_ERR *, PKG_ERR_CODE, char *, ...); +void pkgerr_clear(PKG_ERR *); +int pkgerr_dump(PKG_ERR *, FILE *); +int pkgerr_num(PKG_ERR *); +char *pkgerr_get(PKG_ERR *, int); +void pkgerr_free(PKG_ERR *); + +#ifdef __cplusplus +} +#endif + +#endif /* _PKGERR_H */ diff --git a/usr/src/lib/libpkg/common/pkgexecl.c b/usr/src/lib/libpkg/common/pkgexecl.c new file mode 100644 index 0000000000..e8eda96059 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgexecl.c @@ -0,0 +1,80 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include <wait.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <pwd.h> +#include <grp.h> +#include <fcntl.h> +#include "pkglocale.h" +#include "pkglibmsgs.h" +#include "pkglib.h" + +#define MAXARGS 64 + +/*VARARGS4*/ +int +pkgexecl(char *filein, char *fileout, char *uname, char *gname, ...) +{ + char *arg[MAXARGS+1]; + char *pt; + int n; + va_list ap; + + /* construct arg[] array from varargs passed in */ + + va_start(ap, gname); + + n = 0; + while ((pt = va_arg(ap, char *)) != NULL) { + if (n >= MAXARGS) { + va_end(ap); + progerr(pkg_gt(ERR_TOO_MANY_ARGS), + arg[0] ? arg[0] : "??"); + return (-1); + } + arg[n++] = pt; + } + + arg[n] = NULL; + va_end(ap); + + /* return results of executing command based on arg[] list */ + + return (pkgexecv(filein, fileout, uname, gname, arg)); +} diff --git a/usr/src/lib/libpkg/common/pkgexecv.c b/usr/src/lib/libpkg/common/pkgexecv.c new file mode 100644 index 0000000000..28a7a07250 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgexecv.c @@ -0,0 +1,445 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" + +/* global environment inherited by this process */ +extern char **environ; + +/* dstream.c */ +extern int ds_curpartcnt; +extern int ds_close(int pkgendflg); + +/* + * global internal (private) variables + */ + +/* received signal count - bumped with hooked signals are caught */ + +static int sig_received = 0; + +/* + * Name: sig_trap + * Description: hooked up to signal counts number of signals received + * Arguments: a_signo - [RO, *RO] - (int) + * Integer representing the signal received; see signal(3c) + * Returns: <void> + */ + +static void +sig_trap(int a_signo) +{ + sig_received++; +} + +/* + * Name: pkgexecv + * Description: Asynchronously execute a package command in a separate process + * and return results - the subprocess MUST arm it's own SIGINT + * and SIGHUP signals and must return a standard package command + * exit code (see returns below) + * Only another package command (such as pkginstall, pkgremove, + * etc.) may be called via this interface. No files are closed + * because open files are passed across to certain commands using + * either implicit agreements between the two (yuk!) or by using + * the '-p' option which passes a string of digits, some of which + * represent open file descriptors passed through this interface! + * Arguments: filein - [RO, *RO] - (char *) + * Pointer to string representing the name of the file to + * use for the package commands's stdin + * == (char *)NULL or == "" - the current stdin + * is used for the new package command process + * fileout - [RO, *RO] - (char *) + * Pointer to string representing the name of the file to + * use for the package commands's stdout and stderr + * == (char *)NULL or == "" - the current stdout/stderr + * is used for the new package command process + * uname - [RO, *RO] - (char *) + * Pointer to string representing the user name to execute + * the package command as - the user name is looked up + * using the ncgrpw:cpwnam() interface + * == (char *)NULL or == "" - the user name of the current + * process is used for the new package command process + * gname - [RO, *RO] - (char *) + * Pointer to string representing the group name to execute + * the package command as - the group name is looked up + * using the ncgrpw:cgrnam() interface + * == (char *)NULL or == "" - the group name of the current + * process is used for the new package command process + * arg - [RO, *RO] - (char **) + * Pointer to array of character pointers representing the + * arguments to pass to the package command - the array is + * terminated with a pointer to (char *)NULL + * Returns: int + * == 99 - exec() of package command failed + * == -1 - fork failed or other fatal error during + * execution of the package command + * otherwise - exit code from package command: + * 0 - successful + * 1 - package operation failed (fatal error) + * 2 - non-fatal error (warning) + * 3 - operation interrupted (including SIGINT/SIGHUP) + * 4 - admin settings prevented operation + * 5 - administration required and -n was specified + * IN addition: + * 10 is added to the return code if reboot after the + * installation of all packages is required + * 20 is added to the return code if immediate reboot + * after installation of this package is required + */ + +int +pkgexecv(char *filein, char *fileout, char *uname, char *gname, char *arg[]) +{ + int exit_no; + int n; + int status; + pid_t pid; + pid_t waitstat; + struct group *grp; + struct passwd *pwp; + struct sigaction nact; + struct sigaction oact; + void (*funcSighup)(); + void (*funcSigint)(); + + /* flush standard i/o before creating new process */ + + (void) fflush(stdout); + (void) fflush(stderr); + + /* + * hold SIGINT/SIGHUP signals and reset signal received counter; + * after the vfork() the parent and child need to setup their respective + * interrupt handling and release the hold on the signals + */ + + (void) sighold(SIGINT); + (void) sighold(SIGHUP); + + sig_received = 0; + + /* + * create new process to execute command in; + * vfork() is being used to avoid duplicating the parents + * memory space - this means that the child process may + * not modify any of the parents memory including the + * standard i/o descriptors - all the child can do is + * adjust interrupts and open files as a prelude to a + * call to exec(). + */ + + pid = vfork(); + + if (pid < 0) { + /* + * ************************************************************* + * fork failed! + * ************************************************************* + */ + + progerr(pkg_gt(ERR_FORK_FAILED), errno, strerror(errno)); + + /* release hold on signals */ + + (void) sigrelse(SIGHUP); + (void) sigrelse(SIGINT); + + return (-1); + } + + if (pid > 0) { + /* + * ************************************************************* + * This is the forking (parent) process + * ************************************************************* + */ + + /* close datastream if any portion read */ + + if (ds_curpartcnt >= 0) { + if (ds_close(0) != 0) { + /* kill child process */ + + (void) sigsend(P_PID, pid, SIGKILL); + + /* release hold on signals */ + + (void) sigrelse(SIGHUP); + (void) sigrelse(SIGINT); + + return (-1); + } + } + + /* + * setup signal handlers for SIGINT and SIGHUP and release hold + */ + + /* hook SIGINT to sig_trap() */ + + nact.sa_handler = sig_trap; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + if (sigaction(SIGINT, &nact, &oact) < 0) { + funcSigint = SIG_DFL; + } else { + funcSigint = oact.sa_handler; + } + + /* hook SIGHUP to sig_trap() */ + + nact.sa_handler = sig_trap; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + if (sigaction(SIGHUP, &nact, &oact) < 0) { + funcSighup = SIG_DFL; + } else { + funcSighup = oact.sa_handler; + } + + /* release hold on signals */ + + (void) sigrelse(SIGHUP); + (void) sigrelse(SIGINT); + + /* + * wait for the process to exit, reap child exit status + */ + + for (;;) { + status = 0; + waitstat = waitpid(pid, (int *)&status, 0); + if (waitstat < 0) { + /* waitpid returned error */ + if (errno == EAGAIN) { + /* try again */ + continue; + } + if (errno == EINTR) { + continue; + } + /* error from waitpid: bail */ + break; + } else if (waitstat == pid) { + /* child exit status available */ + break; + } + } + + /* + * reset signal handlers + */ + + /* reset SIGINT */ + + nact.sa_handler = funcSigint; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + (void) sigaction(SIGINT, &nact, (struct sigaction *)NULL); + + /* reset SIGHUP */ + + nact.sa_handler = funcSighup; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + (void) sigaction(SIGHUP, &nact, (struct sigaction *)NULL); + + /* error if child process does not match */ + + if (waitstat != pid) { + progerr(pkg_gt(ERR_WAIT_FAILED), pid, waitstat, status, + errno, strerror(errno)); + return (-1); + } + + /* + * determine final exit code: + * - if signal received, then return interrupted (3) + * - if child exit status is available, return exit child status + * - otherwise return error (-1) + */ + + if (sig_received != 0) { + exit_no = 3; /* interrupted */ + } else if (WIFEXITED(status)) { + exit_no = WEXITSTATUS(status); + } else { + exit_no = -1; /* exec() or other process error */ + } + + return (exit_no); + } + + /* + * ********************************************************************* + * This is the forked (child) process + * ********************************************************************* + */ + + /* reset all signals to default */ + + for (n = 0; n < NSIG; n++) { + (void) sigset(n, SIG_DFL); + } + + /* release hold on signals held by parent before fork() */ + + (void) sigrelse(SIGHUP); + (void) sigrelse(SIGINT); + + /* + * The caller wants to have stdin connected to filein. + */ + + if (filein && *filein) { + /* + * If input is supposed to be connected to /dev/tty + */ + if (strncmp(filein, "/dev/tty", 8) == 0) { + /* + * If stdin is connected to a tty device. + */ + if (isatty(STDIN_FILENO)) { + /* + * Reopen it to /dev/tty. + */ + n = open(filein, O_RDONLY); + if (n >= 0) { + (void) dup2(n, STDIN_FILENO); + } + } + } else { + /* + * If we did not want to be connected to /dev/tty, we + * connect input to the requested file no questions. + */ + n = open(filein, O_RDONLY); + if (n >= 0) { + (void) dup2(n, STDIN_FILENO); + } + } + } + + /* + * The caller wants to have stdout and stderr connected to fileout. + * If "fileout" is "/dev/tty" then reconnect stdout to "/dev/tty" + * only if /dev/tty is not already associated with "a tty". + */ + + if (fileout && *fileout) { + /* + * If output is supposed to be connected to /dev/tty + */ + if (strncmp(fileout, "/dev/tty", 8) == 0) { + /* + * If stdout is connected to a tty device. + */ + if (isatty(STDOUT_FILENO)) { + /* + * Reopen it to /dev/tty if /dev/tty available. + */ + n = open(fileout, O_WRONLY); + if (n >= 0) { + /* + * /dev/tty is available - close the + * current standard output stream, and + * reopen it on /dev/tty + */ + (void) dup2(n, STDOUT_FILENO); + } + } + /* + * not connected to tty device - probably redirect to + * file - preserve existing output device + */ + } else { + /* + * If we did not want to be connected to /dev/tty, we + * connect output to the requested file no questions. + */ + /* LINTED O_CREAT without O_EXCL specified in call to */ + n = open(fileout, O_WRONLY|O_CREAT|O_APPEND, 0666); + if (n >= 0) { + (void) dup2(n, STDOUT_FILENO); + } + } + + /* + * Dup stderr from stdout. + */ + + (void) dup2(STDOUT_FILENO, STDERR_FILENO); + } + + /* + * do NOT close all file descriptors except stdio + * file descriptors are passed in to some subcommands + * (see dstream:ds_getinfo() and dstream:ds_putinfo()) + */ + + /* set group/user i.d. if requested */ + + if (gname && *gname && (grp = cgrnam(gname)) != NULL) { + if (setgid(grp->gr_gid) == -1) { + progerr(pkg_gt(ERR_SETGID), grp->gr_gid); + } + } + if (uname && *uname && (pwp = cpwnam(uname)) != NULL) { + if (setuid(pwp->pw_uid) == -1) { + progerr(pkg_gt(ERR_SETUID), pwp->pw_uid); + } + } + + /* execute target executable */ + + (void) execve(arg[0], arg, environ); + progerr(pkg_gt(ERR_EX_FAIL), arg[0], errno); + _exit(99); + /*NOTREACHED*/ +} diff --git a/usr/src/lib/libpkg/common/pkglib.h b/usr/src/lib/libpkg/common/pkglib.h new file mode 100644 index 0000000000..418cf632f7 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkglib.h @@ -0,0 +1,622 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +#ifndef _PKGLIB_H +#define _PKGLIB_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <limits.h> +#include <stdio.h> +#include <pkgdev.h> +#include <pkgstrct.h> +#include <openssl/bio.h> +#include <openssl/x509.h> +#include <netdb.h> +#include <boot_http.h> +#include "pkgerr.h" +#include "keystore.h" +#include "cfext.h" + +/* + * Virtual File Protocol definitions + */ + +/* + * flags associated with virtual file protocol operations; note that these flags + * may only occupy the low order 16 bits of the 32-bit unsigned flag. + */ + +typedef unsigned long VFPFLAGS_T; + +#define VFP_NONE 0x00000000 /* no special flags */ +#define VFP_NEEDNOW 0x00000001 /* need memory now */ +#define VFP_SEQUENTIAL 0x00000002 /* sequential access */ +#define VFP_RANDOM 0x00000004 /* random access */ +#define VFP_NOMMAP 0x00000008 /* do not use mmap to access file */ +#define VFP_NOMALLOC 0x00000010 /* do not use malloc to buffer file */ + +/* virtual file protocol object */ + +typedef struct _vfp VFP_T; + +/* structure behind the virtual file protocol object */ + +struct _vfp { + FILE *_vfpFile; /* -> opened FILE */ + char *_vfpCurr; /* -> current byte to read/write */ + char *_vfpHighWater; /* -> last byte modified */ + char *_vfpEnd; /* -> last data byte */ + char *_vfpPath; /* -> path associated with FILE */ + char *_vfpStart; /* -> first data byte */ + void *_vfpExtra; /* undefined */ + size_t _vfpSize; /* size of mapped/allocated area */ + size_t _vfpMapSize; /* # mapped bytes */ + VFPFLAGS_T _vfpFlags; /* flags associated with vfp/data */ + int _vfpOverflow; /* non-zero if buffer write overflow */ + blkcnt_t _vfpCkStBlocks; /* checkpoint # blocks */ + dev_t _vfpCkDev; /* checkpoint device i.d. */ + ino_t _vfpCkIno; /* checkpoint inode # */ + off_t _vfpCkSize; /* checkpoint size */ + time_t _vfpCkMtime; /* checkpoint modification time */ +}; + +/* + * get highest modified byte (length) contained in vfp + * + * determine number of bytes to write - it will be the highest of: + * -- the current pointer into the file - this is updated whenever + * the location of the file is changed by a single byte + * -- the last "high water mark" - the last known location that + * was written to the file - updated only when the location + * of the file is directly changed - e.g. vfpSetCurrCharPtr, + * vfpTruncate, vfpRewind. + * this reduces the "bookkeeping" that needs to be done to know + * how many bytes to write out to the file - typically a file is + * written sequentially so the current file pointer is sufficient + * to determine how many bytes to write out. + */ + +#define vfpGetModifiedLen(VFP) \ + (size_t)(((VFP)->_vfpHighWater > (VFP)->_vfpCurr) ? \ + (((ptrdiff_t)(VFP)->_vfpHighWater - \ + (ptrdiff_t)(VFP)->_vfpStart)) : \ + (((ptrdiff_t)(VFP)->_vfpCurr - \ + (ptrdiff_t)(VFP)->_vfpStart))) + +/* + * increment current pointer by specified delta + * if the delta exceeds the buffer size, set pointer to buffer end + */ +#define vfpIncCurrPtrBy(VFP, INC) \ + { \ + ((VFP)->_vfpCurr) += (INC); \ + if (((VFP)->_vfpCurr) > ((VFP)->_vfpEnd)) { \ + (VFP)->_vfpCurr = (VFP)->_vfpEnd; \ + (VFP)->_vfpOverflow = 1; \ + } \ + if ((VFP)->_vfpHighWater < (VFP)->_vfpCurr) { \ + (VFP)->_vfpHighWater = (VFP)->_vfpCurr; \ + } \ + } + +/* get the path associated with the vfp */ +#define vfpGetPath(VFP) ((VFP)->_vfpPath) + +/* get a string from the vfp into a fixed size buffer */ +#define vfpGets(VFP, PTR, LEN) \ + { \ + char *XXpXX = (PTR); \ + size_t XXlXX = (LEN); \ + while ((*(VFP)->_vfpCurr != '\0') && \ + (*(VFP)->_vfpCurr != '\n')) { \ + if (XXlXX > 1) { \ + *XXpXX++ = *(VFP)->_vfpCurr; \ + XXlXX--; \ + } \ + (VFP)->_vfpCurr++; \ + } \ + *XXpXX++ = '\0'; \ + if (*(VFP)->_vfpCurr != '\0') { \ + (VFP)->_vfpCurr++; \ + } \ + } + +/* get number of bytes remaining to read */ +#define vfpGetBytesRemaining(VFP) \ + (((((VFP)->_vfpHighWater) <= ((VFP)->_vfpCurr))) ? 0 : \ + ((((ptrdiff_t)(VFP)->_vfpHighWater)-((ptrdiff_t)(VFP)->_vfpCurr)))) + +/* get number of bytes remaining to write */ +#define vfpGetBytesAvailable(VFP) \ + (((((VFP)->_vfpEnd) <= ((VFP)->_vfpCurr))) ? 0 : \ + ((((ptrdiff_t)(VFP)->_vfpEnd)-((ptrdiff_t)(VFP)->_vfpCurr)))) + +/* put current character and increment to next */ +#define vfpPutc(VFP, C) \ + { \ + (*(VFP)->_vfpCurr) = ((char)(C)); \ + vfpIncCurrPtrBy((VFP), 1); \ + } + +/* put integer to current character and increment */ +#define vfpPutInteger(VFP, NUMBER) vfpPutFormat((VFP), "%d", (NUMBER)) + +/* put long to current character and increment */ +#define vfpPutLong(VFP, NUMBER) vfpPutFormat((VFP), "%ld", (NUMBER)) + +/* get current character and increment to next */ +#define vfpGetc(VFP) (*(VFP)->_vfpCurr++) + +/* get current character - do not increment */ +#define vfpGetcNoInc(VFP) (*(VFP)->_vfpCurr) + +/* get pointer to current character */ +#define vfpGetCurrCharPtr(VFP) ((VFP)->_vfpCurr) + +/* increment current character pointer */ +#define vfpIncCurrPtr(VFP) vfpIncCurrPtrBy((VFP), 1) + +/* decrement current character pointer */ +#define vfpDecCurrPtr(VFP) ((VFP)->_vfpCurr--) + +/* get pointer to first data byte in buffer */ +#define vfpGetFirstCharPtr(VFP) ((VFP)->_vfpStart) + +/* get pointer to last data byte in buffer */ +#define vfpGetLastCharPtr(VFP) ((VFP)->_vfpHighWater) + +/* set pointer to current character */ +#define vfpSetCurrCharPtr(VFP, PTR) \ + if ((VFP)->_vfpCurr > (VFP)->_vfpHighWater) { \ + (VFP)->_vfpHighWater = (VFP)->_vfpCurr; \ + } \ + ((VFP)->_vfpCurr = (PTR)) + +/* set pointer to last data byte in buffer */ +#define vfpSetLastCharPtr(VFP, PTR) \ + if ((PTR) >= (VFP)->_vfpStart) { \ + (VFP)->_vfpHighWater = (PTR); \ + if ((VFP)->_vfpCurr > (VFP)->_vfpHighWater) { \ + (VFP)->_vfpCurr = (VFP)->_vfpHighWater; \ + } \ + } + +/* seek to end of file - one past last data byte in file */ +#define vfpSeekToEnd(VFP) ((VFP)->_vfpCurr = ((VFP)->_vfpHighWater)+1) + +/* get number of bytes between current char and specified char */ +#define vfpGetCurrPtrDelta(VFP, P) \ + (((ptrdiff_t)(P))-((ptrdiff_t)(VFP)->_vfpCurr)) + +/* put string to current character and increment */ +#define vfpPuts(VFP, S) \ + { \ + size_t xxLen; \ + size_t xxResult; \ + xxLen = vfpGetBytesAvailable((VFP)); \ + xxResult = strlcpy(((VFP)->_vfpCurr), (S), xxLen); \ + vfpIncCurrPtrBy((VFP), xxResult); \ + } + +/* put fixed number of bytes to current character and increment */ +#define vfpPutBytes(VFP, PTR, LEN) \ + { \ + size_t xxLen; \ + xxLen = vfpGetBytesAvailable((VFP)); \ + if (xxLen > (LEN)) { \ + xxLen = (LEN); \ + } else { \ + (VFP)->_vfpOverflow = 1; \ + } \ + memcpy((VFP)->_vfpCurr, (PTR), (xxLen)); \ + vfpIncCurrPtrBy((VFP), (xxLen)); \ + } + +/* put format one arg to current character and increment */ +#define vfpPutFormat(VFP, FORMAT, ARG) \ + { \ + char xxTeMpXX[256]; \ + (void) snprintf(xxTeMpXX, sizeof (xxTeMpXX), (FORMAT), (ARG)); \ + vfpPuts((VFP), xxTeMpXX); \ + } + +struct dm_buf { + char *text_buffer; /* start of allocated buffer */ + int offset; /* number of bytes into the text_buffer */ + int allocation; /* size of buffer in bytes */ +}; + +/* This structure is used to hold a dynamically growing string */ + +struct dstr { + char *pc; + int len; + int max; +}; + +/* setmapmode() defines */ +#define MAPALL 0 /* resolve all variables */ +#define MAPBUILD 1 /* map only build variables */ +#define MAPINSTALL 2 /* map only install variables */ +#define MAPNONE 3 /* map no variables */ + +#define NON_ABI_NAMELNGTH 33 /* 32 chars for name + 1 for NULL */ + +#define BLK_SIZE 512 /* size of logical block */ + +/* max length for printed attributes */ +#define ATTR_MAX 80 + +/* + * These three defines indicate that the prototype file contains a '?' + * meaning do not specify this data in the pkgmap entry. + */ +#define CURMODE BADMODE /* current mode has been specified */ +#define CUROWNER BADOWNER /* ... same for owner ... */ +#define CURGROUP BADGROUP /* ... and group. */ + +#define WILDCARD BADMODE >> 1 +#define DB_UNDEFINED_ENTRY "?" + +#define DEFAULT_MODE 0755 +#define DEFAULT_MODE_FILE 0644 +#define DEFAULT_OWNER "root" +#define DEFAULT_GROUP "other" + +#define INST_RELEASE "var/sadm/system/admin/INST_RELEASE" + +#define RANDOM "/dev/urandom" +#define BLOCK 256 + +#define TERM_WIDTH 60 +#define SMALL_DIVISOR 4 +#define MED_DIVISOR 5 +#define LARGE_DIVISOR 10 +#define MED_DWNLD (10 * 1024 * 1024) /* 10 MB */ +#define LARGE_DWNLD (5 * MED_DWNLD) /* 50 MB */ + +#define HTTP "http://" +#define HTTPS "https://" + +#define PKGADD "pkgadd" + +/* Settings for network admin defaults */ + +#define NET_TIMEOUT_DEFAULT 60 +#define NET_RETRIES_DEFAULT 3 +#define NET_TIMEOUT_MIN 1 /* 1 second */ +#define NET_TIMEOUT_MAX (5 * 60) /* 5 minutes */ +#define NET_RETRIES_MIN 1 +#define NET_RETRIES_MAX 10 +#define AUTH_NOCHECK 0 +#define AUTH_QUIT 1 + +/* package header magic tokens */ +#define HDR_PREFIX "# PaCkAgE DaTaStReAm" +#define HDR_SUFFIX "# end of header" + +/* name of security files */ +#define PKGSEC "/var/sadm/security" +#define SIGNATURE_FILENAME "signature" + +#define GROUP "/etc/group" +#define PASSWD "/etc/passwd" + +/* + * The next three mean that no mode, owner or group was specified or that the + * one specified is invalid for some reason. Sometimes this is an error in + * which case it is generally converted to CUR* with a warning. Other times + * it means "look it up" by stating the existing file system object pointred + * to in the prototype file. + */ +#define NOMODE (BADMODE-1) +#define NOOWNER "@" +#define NOGROUP "@" + +/* string comparitor abbreviators */ + +#define ci_streq(a, b) (strcasecmp((a), (b)) == 0) +#define ci_strneq(a, b, c) (strncasecmp((a), (b), (c)) == 0) +#define streq(a, b) (strcmp((a), (b)) == 0) +#define strneq(a, b, c) (strncmp((a), (b), (c)) == 0) + +#ifdef __STDC__ + +extern FILE *epopen(char *cmd, char *mode); +extern char **gpkglist(char *dir, char **pkg, char **catg); +extern int is_not_valid_length(char **category); +extern int is_not_valid_category(char **category, char *progname); +extern int is_same_CATEGORY(char **category, char *installed_category); +extern char **get_categories(char *catg_arg); + +extern void pkglist_cont(char *keyword); +extern char **pkgalias(char *pkg); +extern char *get_prog_name(void); +extern char *set_prog_name(char *name); +extern int averify(int fix, char *ftype, char *path, struct ainfo *ainfo); +extern int ckparam(char *param, char *value); +extern int ckvolseq(char *dir, int part, int nparts); +extern int cverify(int fix, char *ftype, char *path, struct cinfo *cinfo, + int allow_checksum); +extern unsigned long compute_checksum(int *r_cksumerr, char *a_path); +extern int fverify(int fix, char *ftype, char *path, struct ainfo *ainfo, + struct cinfo *cinfo); +extern char *getErrbufAddr(void); +extern int getErrbufSize(void); +extern char *getErrstr(void); +extern void setErrstr(char *errstr); +extern int devtype(char *alias, struct pkgdev *devp); +extern int ds_totread; /* total number of parts read */ +extern int ds_close(int pkgendflg); +extern int ds_findpkg(char *device, char *pkg); +extern int ds_getinfo(char *string); +extern int ds_getpkg(char *device, int n, char *dstdir); +extern int ds_ginit(char *device); +extern boolean_t ds_fd_open(void); +extern int ds_init(char *device, char **pkg, char *norewind); +extern int BIO_ds_dump_header(PKG_ERR *, BIO *); +extern int BIO_ds_dump(PKG_ERR *, char *, BIO *); +extern int BIO_dump_cmd(char *cmd, BIO *bio); +extern int ds_next(char *, char *); +extern int ds_readbuf(char *device); +extern int epclose(FILE *pp); +extern int esystem(char *cmd, int ifd, int ofd); +extern int e_ExecCmdArray(int *r_status, char **r_results, + char *a_inputFile, char *a_cmd, char **a_args); +extern int e_ExecCmdList(int *r_status, char **r_results, + char *a_inputFile, char *a_cmd, ...); +extern int gpkgmap(struct cfent *ept, FILE *fp); +extern int gpkgmapvfp(struct cfent *ept, VFP_T *fpv); +extern void setmapmode(int mode_no); +extern int isFdRemote(int a_fd); +extern int isFstypeRemote(char *a_fstype); +extern int isPathRemote(char *a_path); +extern int iscpio(char *path, int *iscomp); +extern int isdir(char *path); +extern int isfile(char *dir, char *file); +extern int fmkdir(char *a_path, int a_mode); +extern int pkgexecl(char *filein, char *fileout, char *uname, char *gname, + ...); +extern int pkgexecv(char *filein, char *fileout, char *uname, char *gname, + char *arg[]); +extern int pkghead(char *device); +extern int pkgmount(struct pkgdev *devp, char *pkg, int part, int nparts, + int getvolflg); +extern int pkgtrans(char *device1, char *device2, char **pkg, + int options, keystore_handle_t, char *); +extern int pkgumount(struct pkgdev *devp); +extern int ppkgmap(struct cfent *ept, FILE *fp); +extern int putcfile(struct cfent *ept, FILE *fp); +extern int putcvfpfile(struct cfent *ept, VFP_T *vfp); +extern int rrmdir(char *path); +extern void set_memalloc_failure_func(void (*)(int)); +extern void *xmalloc(size_t size); +extern void *xrealloc(void *ptr, size_t size); +extern char *xstrdup(char *str); +extern void set_passphrase_prompt(char *); +extern void set_passphrase_passarg(char *); +extern int pkg_passphrase_cb(char *, int, int, void *); + +extern int srchcfile(struct cfent *ept, char *path, VFP_T *vfp, + VFP_T *vfpout); +extern struct group *cgrgid(gid_t gid); +extern struct group *cgrnam(char *nam); +extern struct passwd *cpwnam(char *nam); +extern struct passwd *cpwuid(uid_t uid); +extern struct group *clgrgid(gid_t gid); +extern struct group *clgrnam(char *nam); +extern struct passwd *clpwnam(char *nam); +extern struct passwd *clpwuid(uid_t uid); +extern void basepath(char *path, char *basedir, char *ir); +extern void canonize(char *file); +extern void canonize_slashes(char *file); +extern void checksum_off(void); +extern void checksum_on(void); +extern void cvtpath(char *path, char *copy); +extern void ds_order(char *list[]); +extern void ds_putinfo(char *buf); +extern void ds_skiptoend(char *device); +extern void ecleanup(void); +/*PRINTFLIKE1*/ +extern void logerr(char *fmt, ...); +extern int mappath(int flag, char *path); +extern int mapvar(int flag, char *varname); +/*PRINTFLIKE1*/ +extern void progerr(char *fmt, ...); +extern void pkgerr(PKG_ERR *); +extern void rpterr(void); +extern void tputcfent(struct cfent *ept, FILE *fp); +extern void set_nonABI_symlinks(void); +extern int nonABI_symlinks(void); +extern void disable_attribute_check(void); +extern int get_disable_attribute_check(void); + +/* security.c */ +extern void sec_init(void); +extern char *get_subject_display_name(X509 *); +extern char *get_issuer_display_name(X509 *); +extern char *get_serial_num(X509 *); +extern char *get_fingerprint(X509 *, const EVP_MD *); +extern int get_cert_chain(PKG_ERR *, X509 *, STACK_OF(X509) *, + STACK_OF(X509) *, STACK_OF(X509) **); + +/* pkgstr.c */ +void pkgstrConvertUllToTimeString_r(unsigned long long a_time, + char *a_buf, int a_bufLen); +char *pkgstrConvertPathToBasename(char *a_path); +char *pkgstrConvertPathToDirname(char *a_path); +char *pkgstrDup(char *a_str); +char *pkgstrLocatePathBasename(char *a_path); +void pkgstrScaleNumericString(char *a_buf, unsigned long long scale); +void pkgstrAddToken(char **a_old, char *a_new, char a_separator); +boolean_t pkgstrContainsToken(char *a_string, char *a_token, + char *a_separators); +void pkgstrExpandTokens(char **a_old, char *a_string, + char a_separator, char *a_separators); +char *pkgstrGetToken(char *r_sep, char *a_string, int a_index, + char *a_separators); +void pkgstrGetToken_r(char *r_sep, char *a_string, int a_index, + char *a_separators, char *a_buf, int a_bufLen); +unsigned long pkgstrNumTokens(char *a_string, char *a_separators); +char *pkgstrPrintf(char *a_format, ...); +void pkgstrPrintf_r(char *a_buf, int a_bufLen, char *a_format, ...); +void pkgstrRemoveToken(char **r_string, char *a_token, + char *a_separators, int a_index); +void pkgstrRemoveLeadingWhitespace(char **a_str); +/* vfpops.c */ +extern int vfpCheckpointFile(VFP_T **r_destVfp, VFP_T **a_vfp, + char *a_path); +extern int vfpCheckpointOpen(VFP_T **a_cvfp, VFP_T **r_vfp, char *a_path, + char *a_mode, VFPFLAGS_T a_flags); +extern int vfpClearModified(VFP_T *a_vfp); +extern int vfpClose(VFP_T **r_vfp); +extern int vfpGetModified(VFP_T *a_vfp); +extern int vfpOpen(VFP_T **r_vfp, char *a_path, char *a_mode, + VFPFLAGS_T a_flags); +extern void vfpRewind(VFP_T *a_vfp); +extern ssize_t vfpSafePwrite(int a_fildes, void *a_buf, + size_t a_nbyte, off_t a_offset); +extern ssize_t vfpSafeWrite(int a_fildes, void *a_buf, size_t a_nbyte); +extern int vfpSetFlags(VFP_T *a_vfp, VFPFLAGS_T a_flags); +extern int vfpSetModified(VFP_T *a_vfp); +extern int vfpSetSize(VFP_T *a_vfp, size_t a_size); +extern void vfpTruncate(VFP_T *a_vfp); +extern int vfpWriteToFile(VFP_T *a_vfp, char *a_path); + +/* handlelocalfs.c */ +boolean_t enable_local_fs(void); +boolean_t restore_local_fs(void); + +#else /* __STDC__ */ + +extern FILE *epopen(); +extern void pkglist_cont(); +extern char **gpkglist(); +extern char **pkgalias(); +extern char *get_prog_name(); +extern char *set_prog_name(); +extern int averify(); +extern int ckparam(); +extern int ckvolseq(); +extern int cverify(); +extern unsigned long compute_checksum(); +extern int fverify(); +extern char *getErrbufAddr(); +extern int getErrbufSize(); +extern char *getErrstr(); +extern void setErrstr(); +extern int devtype(); +extern int ds_close(); +extern int ds_findpkg(); +extern int ds_getinfo(); +extern int ds_getpkg(); +extern int ds_ginit(); +extern boolean_t ds_fd_open(); +extern int ds_init(); +extern int ds_next(); +extern int ds_readbuf(); +extern int epclose(); +extern int esystem(); +extern int e_ExecCmdArray(); +extern int e_ExecCmdList(); +extern int gpkgmap(); +extern int isFdRemote(); +extern int isFstypeRemote(); +extern int isPathRemote(); +extern int iscpio(); +extern int isdir(); +extern int isfile(); +extern int pkgexecl(); +extern int pkgexecv(); +extern int pkghead(); +extern int pkgmount(); +extern int pkgtrans(); +extern int pkgumount(); +extern int ppkgmap(); +extern int putcfile(); +extern int putcvfpfile(); +extern int rrmdir(); +extern int srchcfile(); +extern struct group *cgrgid(); +extern struct group *cgrnam(); +extern struct passwd *cpwnam(); +extern struct passwd *cpwuid(); +extern void basepath(); +extern void canonize(); +extern void canonize_slashes(); +extern void checksum_off(); +extern void checksum_on(); +extern void cvtpath(); +extern void ds_order(); +extern void ds_putinfo(); +extern void ds_skiptoend(); +extern void ecleanup(); +extern void logerr(); +extern int mappath(); +extern int mapvar(); +extern void progerr(); +extern void rpterr(); +extern void tputcfent(); +extern void set_nonABI_symlinks(); +extern int nonABI_symlinks(); +extern void disable_attribute_check(); +extern int get_disable_attribute_check(); +/* vfpops.c */ +extern int vfpCheckpointFile(); +extern int vfpCheckpointOpen(); +extern int vfpClearModified(); +extern int vfpClose(); +extern int vfpGetModified(); +extern int vfpOpen(); +extern void vfpRewind(); +extern int vfpSetFlags(); +extern int vfpSetModified(); +extern int vfpSetSize(); +extern void vfpTruncate(); +extern int vfpWriteToFile(); + +/* handlelocalfs.c */ +boolean_t enable_local_fs(); +boolean_t restore_local_fs(); + +/* gpkgmap.c */ +int getmapmode(void); + +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* _PKGLIB_H */ diff --git a/usr/src/lib/libpkg/common/pkglibmsgs.h b/usr/src/lib/libpkg/common/pkglibmsgs.h new file mode 100644 index 0000000000..cf77b0f57a --- /dev/null +++ b/usr/src/lib/libpkg/common/pkglibmsgs.h @@ -0,0 +1,431 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _PKGLIBMSGS_H +#define _PKGLIBMSGS_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/* srchcfile messages */ +#define ERR_MISSING_NEWLINE "missing newline at end of entry" +#define ERR_ILLEGAL_SEARCH_PATH "illegal search path specified" +#define ERR_CANNOT_READ_MM_NUMS "unable to read major/minor device numbers" +#define ERR_INCOMPLETE_ENTRY "incomplete entry" +#define ERR_VOLUMENO_UNEXPECTED "volume number not expected" +#define ERR_FTYPE_I_UNEXPECTED "ftype <i> not expected" +#define ERR_CANNOT_READ_CLASS_TOKEN "unable to read class token" +#define ERR_CANNOT_READ_PATHNAME_FLD "unable to read pathname field" +#define ERR_UNKNOWN_FTYPE "unknown ftype" +#define ERR_CANNOT_READ_LL_PATH "unable to read local/link path" +#define ERR_INCOMPLETE_ENTRY "incomplete entry" +#define ERR_NO_LINK_SOURCE_SPECIFIED "no link source specified" +#define ERR_CANNOT_READ_MOG "unable to read mode/owner/group" +#define ERR_CANNOT_READ_CONTENT_INFO "unable to read content info" +#define ERR_PACKAGE_NAME_TOO_LONG "package name too long" +#define ERR_NO_MEMORY "no memory for package information" +#define ERR_BAD_ENTRY_END "bad end of entry" +#define ERR_EXTRA_TOKENS "extra token(s) on input line" + +/* pkgtrans messages */ +#define MSG_TRANSFER "Transferring <%s> package instance\n" +#define MSG_STORE_ACC "Retrieving signature certificates from <%s>\n" +#define MSG_SIGNING "Generating digital signature for signer <%s>\n" +#define MSG_RENAME "\t... instance renamed <%s> on destination\n" + +#define ERR_TRANSFER "unable to complete package transfer" +#define MSG_SEQUENCE "- volume is out of sequence" +#define MSG_MEM "- no memory" +#define MSG_CMDFAIL "- process <%s> failed, exit code %d" +#define MSG_POPEN "- popen of <%s> failed, errno=%d" +#define MSG_PCLOSE "- pclose of <%s> failed, errno=%d" +#define MSG_BADDEV "- invalid or unknown device <%s>" +#define MSG_GETVOL "- unable to obtain package volume" +#define MSG_NOSIZE "- unable to obtain maximum part size from pkgmap" +#define MSG_CHDIR "- unable to change directory to <%s>" +#define MSG_SYMLINK "- unable to create symbolic link to <%s> from <%s>" +#define MSG_STATDIR "- unable to stat <%s>" +#define MSG_CHOWNDIR "- unable to chown <%s>" +#define MSG_CHMODDIR "- unable to chmod <%s>" +#define MSG_FSTYP "- unable to determine filesystem type for <%s>" +#define MSG_NOTEMP "- unable to create or use temporary directory <%s>" +#define MSG_SAMEDEV "- source and destination represent the same device" +#define MSG_NOTMPFIL "- unable to create or use temporary file <%s>" +#define MSG_NOPKGMAP "- unable to open pkgmap for <%s>" +#define MSG_BADPKGINFO "- unable to determine contents of pkginfo file" +#define MSG_NOPKGS "- no packages were selected from <%s>" +#define MSG_MKDIR "- unable to make directory <%s>" +#define MSG_NOEXISTS "- package instance <%s> does not exist on source " \ + "device" +#define MSG_EXISTS "- no permission to overwrite existing path <%s>" +#define MSG_DUPVERS "- identical version of <%s> already exists on " \ + "destination device" +#define MSG_TWODSTREAM "- both source and destination devices cannot be a " \ + "datastream" +#define MSG_OPEN "- open of <%s> failed, errno=%d" +#define MSG_STATVFS "- statvfs(%s) failed, errno=%d" + +/* security problems */ +#define ERR_PARSE "unable to parse keystore <%s>, invalid " \ + "format or corrupt" +#define ERR_BADPASS "Invalid password. Password does not " \ + "decrypt keystore" + +#define MSG_PASSWD_FILE "Password file <%s> cannot be read" +#define MSG_PASSWD_AGAIN "For Verification" +#define MSG_PASSWD_NOMATCH "Passwords do not match" +#define MSG_BADPASSARG "Password retrieval method <%s> invalid" +#define MSG_NOPASS "Cannot get passphrase using " \ + "retrieval method <%s>" + +#define ERR_MISMATCHPASS "<%s> encrypted with different password " \ + " than <%s>, keystore <%s> corrupt" + +#define MSG_CHSIGDIR "- unable to change directory to <%s/%s>" +#define MSG_MKSIGDIR "- unable to make directory <%s/%s>" +#define ERR_CANTSIGN "- destination device must be datastream in order to" \ + " sign contents" +#define ERR_STORE "unable to find or use store <%s> from application " \ + "<%s>:<%s>" + +#define ERR_NO_KEYSTORE "unable to open keystore <%s> for reading" +#define ERR_NOT_REG "<%s> is not a regular file" +#define ERR_KEYSTORE_CORRUPT "Keystore file <%s> is corrupt or unparseable" +#define ERR_KEYSTORE_REPAIR "unable to repair keystore <%s>" +#define ERR_KEYSTORE_LOCKED_READ "unable to lock keystore file <%s> " \ + "for reading, try again later" +#define ERR_KEYSTORE_LOCKED "unable to lock keystore <%s> for exclusive " \ + "access" +#define ERR_KEYSTORE_UNLOCK "unable to unlock keystore <%s> for " \ + "application <%s>" +#define ERR_KEYSTORE_WRITE "unable to open keystore <%s> for writing" +#define ERR_KEYSTORE_REMOVE "unable to delete keystore file <%s>" +#define ERR_KEYSTORE_READ "unable to open keystore <%s> for reading" +#define ERR_KEYSTORE_OPEN "unable to open keystore <%s>:<%s>" +#define ERR_KEYSTORE_FORM "unable to form PKCS12 keystore file for " \ + "writing to <%s>" + +#define ERR_KEYSTORE_NOPUBCERTS "unable to find any public key certificates " \ + "in keystore file <%s>" + +#define ERR_KEYSTORE_NOPRIVKEYS "unable to find any private keys in keystore "\ + "file <%s>" + +#define ERR_KEYSTORE_NOCACERTS "unable to find any trusted certificates in "\ + "file <%s>" + +#define ERR_KEYSTORE_NOTRUST "unable to find any trusted certificates in "\ + "keystore" + +#define ERR_KEYSTORE_NOMATCH "unable to find certificate and key pair " \ + "with alias <%s> in keystore" + +#define ERR_KEYSTORE_DUPLICATECERT "Certificate with alias <%s> " \ + "already exists in keystore" +#define ERR_KEYSTORE_DUPLICATEKEY "Private key with alias <%s> already" \ + " exists in keystore" +#define ERR_KEYSTORE_NO_ALIAS "Keystore certificate <%s> has no recorded " \ + "alias, must be deleted from keystore" +#define ERR_KEYSTORE_NOCERT "No certificate with alias <%s> found in " \ + "keystore <%s>" +#define ERR_KEYSTORE_NOCERTKEY "No certificates or private keys with alias " \ + "<%s> found in keystore <%s>" + +#define ERR_KEYSTORE_INTERNAL "Internal Error file %s line %d" + +#define ERR_CURR_TIME "Cannot determine current time from system" +#define ERR_CERT_TIME "Certificate <%s> has expired or is not yet valid.\n" \ + "Current time: <%s>\n Certificate valid: <%s> - <%s>" +#define ERR_MISMATCHED_KEYS "Private key does not match public key in " \ + "certificate <%s>" +#define ERR_CERT_TIME_BAD "Certificate has corrupt validity dates, " \ + "cannot process" +#define ERR_TRUSTSTORE "unable to find or use trusted certificate " \ + "store <%s> from application <%s>:<%s>" + +#define ERR_STORE_PW "unable to read password from <%s>" + +#define ERR_SEC "unable to sign package contents using <%s> " \ + "private key" + +#define ERR_NOGEN "unable to generate digital signature" + +#define ERR_STORE_PW "unable to read password from <%s>" +#define ERR_CORRUPTSIG "Invalid or corrupt signature in datastream <%s>" +#define ERR_CORRUPTSIG_TYPE "Wrong PKCS7 signature type in datastream <%s>" +#define ERR_CORRUPTSIG_DT "Signature found but not detached in " \ + "datastream <%s>" +#define ERR_KEYSTORE "invalid or corrupt PKCS12 file <%s>." +#define ERR_KEYSTORE_NOCERTS "Store <%s> contains no certificates" +#define ERR_KEYSTORE_NOKEYS "Store <%s> contains no private keys" +#define ERR_SIG_INT "Internal error during signature verification." +#define MSG_VERIFY "## Verifying signature for signer <%s>" +#define MSG_VERIFY_OK "## Signature for signer <%s> verified." +#define ERR_VERIFY "Signature verification failed." +#define ERR_VERIFY_SIG "Signature verification failed while verifying " \ + "certificate <subject=%s, issuer=%s>:<%s>." +#define ERR_VERIFY_ISSUER "Could not find issuer certificate for signer <%s>" +#define ERR_OPENSIG "Signature found in datastream but cannot be " \ + " opened: <%s>" + +#define ERR_SIGFOUND "signature found in datastream <%s>, you must " \ + "specify a keystore with -k" +#define ERR_DSINIT "could not process datastream from <%s>" + +#define MSG_KEYSTORE_AL "Keystore Alias" +#define MSG_KEYSTORE_SN "Serial Number" +#define MSG_KEYSTORE_FP "Fingerprint" +#define MSG_KEYSTORE_CN "Common Name" +#define MSG_KEYSTORE_IN "Issuer Common Name" +#define MSG_KEYSTORE_VD "Validity Dates" +#define MSG_KEYSTORE_TY "Certificate Type" +#define MSG_KEYSTORE_TRUSTED "Trusted Certificate" +#define MSG_KEYSTORE_UNTRUSTED "Signing Certificate" +#define MSG_KEYSTORE_UNKNOWN "Unknown" + +/* parameter errors */ +#define ERR_LEN "length of parameter value <%s> exceeds limit" +#define ERR_ASCII "parameter <%s> must be ascii" +#define ERR_ALNUM "parameter <%s> must be alphanumeric" +#define ERR_CHAR "parameter <%s> has incorrect first character" +#define ERR_UNDEF "parameter <%s> cannot be null" + +/* volume sequence errors */ +#define MSG_SEQ "Volume is out of sequence." +#define MSG_CORRUPT "Volume is corrupt or is not part of the appropriate " \ + "package." +#define ERR_NOPKGMAP "ERROR: unable to process <%s>" +#define ERR_BADPKGINFO "ERROR: unable to process <%s>" + +/* datastream processing errors */ +#define ERR_UNPACK "attempt to process datastream failed" +#define ERR_DSTREAMSEQ "datastream sequence corruption" +#define ERR_TRANSFER "unable to complete package transfer" +#define MSG_CMDFAIL "- process <%s> failed, exit code %d" +#define MSG_TOC "- bad format in datastream table-of-contents" +#define MSG_EMPTY "- datastream table-of-contents appears to be empty" +#define MSG_POPEN "- popen of <%s> failed, errno=%d" +#define MSG_OPEN "- open of <%s> failed, errno=%d" +#define MSG_PCLOSE "- pclose of <%s> failed, errno=%d" +#define MSG_PKGNAME "- invalid package name in datastream table-of-contents" +#define MSG_NOPKG "- package <%s> not in datastream" +#define MSG_STATFS "- unable to stat filesystem, errno=%d" +#define MSG_NOSPACE "- not enough space, %d blocks required, %d available" + +/* pkglist errors */ +#define ERR_MEMORY "memory allocation failure, errno=%d" +#define ERR_NOPKG "no package associated with <%s>" +#define HEADER "The following packages are available:" +#define HELP "Please enter the package instances you wish to " \ + "process from the list provided (or 'all' to process " \ + "all packages.)" + +#define PROMPT "Select package(s) you wish to process (or 'all' to " \ + "process all packages)." +/* pkgmap errors */ +#define ERR_READLINK "unable to read link specification." +#define ERR_NOVAR "no value defined for%s variable <%s>." +#define ERR_OWNTOOLONG "owner string is too long." +#define ERR_GRPTOOLONG "group string is too long." +#define ERR_IMODE "mode must not be parametric at install time." +#define ERR_BASEINVAL "invalid base for mode." +#define ERR_MODELONG "mode string is too long." +#define ERR_MODEALPHA "mode is not numeric." +#define ERR_MODEBITS "invalid bits set in mode." + +/* package mount errors and msgs */ +#define ERR_FSTYP "unable to determine fstype for <%s>" +#define ERR_NOTROOT "You must be \"root\" when using mountable media." +#define MOUNT "/sbin/mount" +#define UMOUNT "/sbin/umount" +#define FSTYP "/usr/sbin/fstyp" + +#define LABEL0 "Insert %%v %d of %d for <%s> package into %%p." +#define LABEL1 "Insert %%v %d of %d into %%p." +#define LABEL2 "Insert %%v for <%s> package into %%p." +#define LABEL3 "Insert %%v into %%p." + +/* package verify errors */ +#define MSG_WLDDEVNO "NOTE: <%s> created as device (%d, %d)." + +#define WRN_QV_SIZE "WARNING: quick verify of <%s>; wrong size." +#define WRN_QV_MTIME "WARNING: quick verify of <%s>; wrong mod time." + +#define ERR_PKG_INTERNAL "Internal package library failure file %s line %d" +#define ERR_UNKNOWN "unable to determine object type" +#define ERR_EXIST "pathname does not exist" +#define ERR_FTYPE "file type <%c> expected <%c> actual" +#define ERR_FTYPED "<%s> is a door and is not being modified" +#define ERR_LINK "pathname not properly linked to <%s>" +#define ERR_SLINK "pathname not symbolically linked to <%s>" +#define ERR_MTIME "modtime <%s> expected <%s> actual" +#define ERR_SIZE "file size <%llu> expected <%llu> actual" +#define ERR_CKSUM "file cksum <%ld> expected <%ld> actual" +#define ERR_NO_CKSUM "unable to checksum, may need to re-run command as " \ + "user \"root\"" +#define ERR_MAJMIN "major/minor device <%d, %d> expected <%d, %d> actual" +#define ERR_PERM "permissions <%04o> expected <%04o> actual" +#define ERR_GROUP "group name <%s> expected <%s> actual" +#define ERR_OWNER "owner name <%s> expected <%s> actual" +#define ERR_MODFAIL "unable to fix modification time" +#define ERR_LINKFAIL "unable to create link to <%s>" +#define ERR_LINKISDIR "<%s> is a directory, link() not performed" +#define ERR_SLINKFAIL "unable to create symbolic link to <%s>" +#define ERR_DIRFAIL "unable to create directory" +#define ERR_CDEVFAIL "unable to create character-special device" +#define ERR_BDEVFAIL "unable to create block-special device" +#define ERR_PIPEFAIL "unable to create named pipe" +#define ERR_ATTRFAIL "unable to fix attributes" +#define ERR_BADGRPID "unable to determine group name for gid <%d>" +#define ERR_BADUSRID "unable to determine owner name for uid <%d>" +#define ERR_BADGRPNM "group name <%s> not found in group table(s)" +#define ERR_BADUSRNM "owner name <%s> not found in passwd table(s)" +#define ERR_GETWD "unable to determine current working directory" +#define ERR_CHDIR "unable to change current working directory to <%s>" +#define ERR_RMDIR "unable to remove existing directory at <%s>" + +/* others */ +#define ERR_ISCPIO_OPEN "iscpio(): open(%s) failed!" +#define ERR_ISCPIO_FSTAT "iscpio(): fstat(%s) failed!" +#define ERR_ISCPIO_READ "iscpio(): read(%s) failed!" +#define ERR_ISCPIO_NOCPIO "iscpio(): <%s> is not a cpio archive!" + +#define ERR_DUPFAIL "%s: strdup(%s) failed.\n" +#define ERR_ADDFAIL "%s: add_cache() failed.\n" +#define ERR_BADMEMB "%s: %s in \"%s\" %s structure is invalid.\n" +#define ERR_NOGRP "dup_gr_ent(): no group entry provided.\n" +#define ERR_NOPWD "dup_pw_ent(): no passwd entry provided.\n" +#define ERR_NOINIT "%s: init_cache() failed.\n" +#define ERR_MALLOC "%s: malloc(%d) failed for %s.\n" + +#define ERR_TOO_MANY_ARGS "too many arguments passed to pkgexecl " \ + "for command <%s>" +#define ERR_WAIT_FAILED "wait for process %ld failed, pid <%ld> status " \ + "<0x%08lx> errno <%d> (%s)" +#define ERR_FORK_FAILED "fork() failed errno=%d (%s)" +#define ERR_FREOPEN "freopen(%s, \"%s\", %s) failed, errno=%d (%s)" +#define ERR_FDOPEN "fdopen(%d, \"%s\") failed, errno=%d (%s)" +#define ERR_CLOSE "close(%d) failed, errno=%d" +#define ERR_SETGID "setgid(%d) failed." +#define ERR_SETUID "setuid(%d) failed." +#define ERR_EX_FAIL "exec of %s failed, errno=%d" + +/* pkgweb errors */ +#define MSG_DWNLD "\n## Downloading..." +#define ERR_DWNLD_FAILED "\n## After %d retries, unable to complete transfer" +#define MSG_DWNLD_TIMEOUT "\n## Timed out, retrying..." +#define MSG_DWNLD_CONNREF "\n## Connection to <%s> refused, retrying..." +#define MSG_DWNLD_HOSTDWN "\n## <%s> not responding, retrying..." +#define MSG_DWNLD_PART "\n## Found partially downloaded file <%s> of " \ + "size <%ld> bytes. To force a complete " \ + "re-download, delete this file and try again" +#define MSG_DWNLD_PREV "\n## Using previously spooled package datastream <%s>" +#define MSG_DWNLD_CONT "\n## Continuing previously attempted download..." +#define MSG_DWNLD_COMPLETE "## Download Complete\n" + +#define ERR_DWNLD_NO_CONT "unable to open partially downloaded file <%s> " \ + "for appending" +#define ERR_BAD_PATH "unable to locate keystore." +#define ERR_EMPTYPATH "No valid path exists for the keystore file." +#define ERR_RETRIES "The number of server retries is not a valid " \ + "value. Please specify a value within the range of %d - %d." +#define ERR_TIMEOUT "The network timeout value is not a valid " \ + "value. Please specify a value within the range of %d - %d." +#define ERR_PARSE_URL "unable to parse the url <%s>." +#define ERR_MEM "unable to allocate memory." +#define ERR_HTTPS_PASSWD "unable set password for HTTPS connection." +#define ERR_HTTPS_CA "unable to set CA file for HTTPS connection." +#define ERR_HTTP "Failure occurred with http(s) negotiation: <%s>" +#define ERR_WRITE "Cannot write to file <%s> : <%s>" +#define ERR_READ "Cannot read from file <%s> : <%s>" +#define ERR_SVR_RESP "unable to establish a connection with the http(s) server." +#define ERR_INIT_CONN "unable to establish a connection with <%s>." +#define ERR_INIT_SESS "unable to intialize download session for <%s>." +#define ERR_INIT_CONN_PROXY "unable to establish a connection with <%s> " \ + "using <%s> as the proxy" +#define ERR_CLOSE_CONN "unable to close the connection with <%s>." +#define ERR_NO_HEAD_VAL "HTTP Response did not include header <%s>." +/* CSTYLED */ +#define ERR_BAD_HEAD_VAL "HTTP Header value \"<%s>: <%s>\" unusable or " \ + "unparseable." +#define ERR_BAD_CONTENT "The package <%s> attempting to be installed " \ + "is illegal." +#define ERR_DWNLD "unable to download package datastream from <%s>." +#define ERR_OPEN_TMP "unable to open temporary file for writing." +#define ERR_WRITE_TMP "unable to write to temporary file." +#define ERR_DISK_SPACE "Not enough disk space is available to download " \ + "package to\n%s. %llukb needed, %llukb available." +#define ERR_CERTS "unable to find a valid certificate in <%s>." +#define ERR_CERTCHAIN "unable to build certificate chain for subject <%s>:<%s>." +#define ERR_ILL_ENV "The environment variable <%s=%s> is illegal" +#define ERR_BAD_PROXY "Invalid proxy specification: <%s>" +#define ERR_TMPDIR "unable to find temporary directory <%s>" +#define ERR_MEM "unable to allocate memory." +#define ERR_NO_DWNLD_DIR "No download directory available." +#define MSG_OCSP_VERIFY "## Contacting OCSP Responder <%s> for " \ + "certificate <%s> status" +#define MSG_OCSP_VERIFY_PROXY "## Contacting OCSP Responder <%s> through " \ + "proxy <%s:%d> for certificate <%s> status" +#define ERR_OCSP_PARSE "OCSP Responder URL <%s> invalid or unparseable" +#define ERR_OCSP_RESP_PARSE "OCSP Response <%s> unparseable or empty" +#define ERR_OCSP_RESP_NOTOK "OCSP Request failed. Expected status " \ + "<%d>, got <%d>, Reason=<%s>" +#define WRN_OCSP_RESP_NONCE "WARNING: Invalid or no nonce found in " \ + "OCSP response." +#define ERR_OCSP_RESP_TYPE "OCSP response message type invalid: <%s>, " \ + "expecting <%s>" +#define ERR_OCSP_CONNECT "Cannot connect to OCSP Responder <%s> port <%d>" +#define ERR_OCSP_SEND "Cannot send OCSP request to OCSP Responder <%s>" +#define ERR_OCSP_READ "Cannot read OCSP response from OCSP Responder <%s>" +#define ERR_OCSP_RESPONDER "OCSP Responder cannot process OCSP Request" +#define ERR_OCSP_UNSUP "Unsupported OCSP Option <%s>" +#define ERR_OCSP_VERIFY_NOTIME "Cannot access system time() to determine " \ + "OCSP Response validity" +#define ERR_OCSP_VERIFY_SIG "OCSP Response, signed by <%s>, cannot be " \ + "verified: <%s>" +#define ERR_OCSP_VERIFY_FAIL "unable to validate response from OCSP " \ + "Responder <%s>" +#define ERR_OCSP_VERIFY_NO_STATUS "OCSP Responder did not supply validity " \ + "of certificate <%s> " +#define ERR_OCSP_VERIFY_VALIDITY_NOTBEFORE "OCSP Response is only valid " \ + "after <%s>. Current time is <%s>." +#define ERR_OCSP_VERIFY_VALIDITY "OCSP Response is only valid from <%s> " \ + "to <%s>. Current time is <%s>." +#define ERR_OCSP_VERIFY_STATUS "OCSP Responder indicates certificate <%s> " \ + "status is <%s>" +#define ERR_OCSP_VERIFY "OCSP Responder rejected certificate, or did not " \ + "recognize" +#define ERR_OCSP_NO_URI "No OCSP Responder URL" + +#define MSG_BASE_USED "Using <%s> as the package base directory." + +#ifdef __cplusplus +} +#endif + +#endif /* _PKGLIBMSGS_H */ diff --git a/usr/src/lib/libpkg/common/pkglocale.h b/usr/src/lib/libpkg/common/pkglocale.h new file mode 100644 index 0000000000..93f57a07d5 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkglocale.h @@ -0,0 +1,51 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _PKGLOCALE_H +#define _PKGLOCALE_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <locale.h> +#include <libintl.h> + +#if !defined(TEXT_DOMAIN) +#define TEXT_DOMAIN "SYS_TEST" +#endif +#ifdef lint +#define pkg_gt(x) x +#else /* !lint */ +#define pkg_gt(x) dgettext(TEXT_DOMAIN, x) +#endif /* lint */ + +#ifdef __cplusplus +} +#endif + +#endif /* _PKGLOCALE_H */ diff --git a/usr/src/lib/libpkg/common/pkgmount.c b/usr/src/lib/libpkg/common/pkgmount.c new file mode 100644 index 0000000000..e3c560ec70 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgmount.c @@ -0,0 +1,167 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <pkgdev.h> +#include <pkginfo.h> +#include <sys/types.h> +#include <devmgmt.h> +#include <sys/mount.h> +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" + +extern void quit(int retcode); /* Expected to be declared by caller! */ +/* libadm.a */ +extern int getvol(char *device, char *label, int options, char *prompt); + +#define CMDSIZ 256 + +int +pkgmount(struct pkgdev *devp, char *pkg, int part, int nparts, int getvolflg) +{ + int n; + char *pt, prompt[64], cmd[CMDSIZ]; + FILE *pp; + + if (getuid()) { + progerr(pkg_gt(ERR_NOTROOT)); + return (99); + } + + if (part && nparts) { + if (pkg) { + (void) sprintf(prompt, pkg_gt(LABEL0), part, + nparts, pkg); + } else { + (void) sprintf(prompt, pkg_gt(LABEL1), part, + nparts); + } + } else if (pkg) + (void) sprintf(prompt, pkg_gt(LABEL2), pkg); + else + (void) sprintf(prompt, pkg_gt(LABEL3)); + + n = 0; + for (;;) { + if (!getvolflg && n) + /* + * Return to caller if not prompting + * and error was encountered. + */ + return (-1); + if (getvolflg && (n = getvol(devp->bdevice, NULL, + (devp->rdonly ? 0 : DM_FORMFS|DM_WLABEL), prompt))) { + if (n == 3) + return (3); + if (n == 2) + progerr(pkg_gt("unknown device <%s>"), + devp->bdevice); + else + progerr( + pkg_gt("unable to obtain package volume")); + return (99); + } + + if (devp->fstyp == NULL) { + (void) sprintf(cmd, "%s %s", FSTYP, devp->bdevice); + if ((pp = epopen(cmd, "r")) == NULL) { + rpterr(); + logerr(pkg_gt(ERR_FSTYP), devp->bdevice); + n = -1; + continue; + } + cmd[0] = '\0'; + if (fgets(cmd, CMDSIZ, pp) == NULL) { + logerr(pkg_gt(ERR_FSTYP), devp->bdevice); + (void) pclose(pp); + n = -1; + continue; + } + if (epclose(pp)) { + rpterr(); + logerr(pkg_gt(ERR_FSTYP), devp->bdevice); + n = -1; + continue; + } + if (pt = strpbrk(cmd, " \t\n")) + *pt = '\0'; + if (cmd[0] == '\0') { + logerr(pkg_gt(ERR_FSTYP), devp->bdevice); + n = -1; + continue; + } + devp->fstyp = strdup(cmd); + } + + if (devp->rdonly) { + n = pkgexecl(NULL, NULL, NULL, NULL, MOUNT, "-r", "-F", + devp->fstyp, devp->bdevice, devp->mount, NULL); + } else { + n = pkgexecl(NULL, NULL, NULL, NULL, MOUNT, "-F", + devp->fstyp, devp->bdevice, devp->mount, NULL); + } + if (n) { + progerr(pkg_gt("mount of %s failed"), devp->bdevice); + continue; + } + devp->mntflg++; + break; + } + return (0); +} + +int +pkgumount(struct pkgdev *devp) +{ + int n = 1; + int retry = 10; + + if (!devp->mntflg) + return (0); + + while (n != 0 && retry-- > 0) { + n = pkgexecl(NULL, NULL, NULL, NULL, UMOUNT, devp->bdevice, + NULL); + if (n != 0) { + progerr(pkg_gt("retrying umount of %s"), + devp->bdevice); + sleep(5); + } + } + if (n == 0) + devp->mntflg = 0; + return (n); +} diff --git a/usr/src/lib/libpkg/common/pkgstr.c b/usr/src/lib/libpkg/common/pkgstr.c new file mode 100644 index 0000000000..8df053e570 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgstr.c @@ -0,0 +1,1132 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +/* + * Module: pkgstr.c + * Synopsis: general string services + * Taxonomy: project private + * Debug Flag: str + * Description: + * + * This module implements general string utility services + * + * Public Methods: + * + * pkgstrAddToken - Add a token to a string + * pkgstrContainsToken - Determine if a string contains a specified token + * pkgstrConvertPathToBasename - Return copy of base name in path string + * pkgstrConvertPathToDirname - Return copy of directory name in path string + * pkgstrConvertUllToTimeString_r - convert unsigned long long to time string + * pkgstrExpandTokens - Expand tokens from string appending tokens to another + * pkgstrGetToken - Get a token from a string + * pkgstrGetToken_r - Get a token from a string into a fixed buffer + * pkgstrLocatePathBasename - Locate position of base name in path string + * pkgstrNumTokens - Determine number of tokens in string + * pkgstrPrintf - Create a string from a printf style format and arguments + * pkgstrPrintf_r - Create a string from a printf style format and arguments + * into a fixed buffer + * pkgstrRemoveToken - Remove a token from a string + * pkgstrRemoveLeadingWhitespace - remove leading whitespace from string + * pkgstrScaleNumericString - Convert unsigned long long to human + * readable form + */ + +/* + * Unix Includes + */ + +#define __EXTENSIONS__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libintl.h> +#include <limits.h> +#include <sys/types.h> +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <ctype.h> +#include <unistd.h> +#include <strings.h> +#include <stdarg.h> + +/* + * pkglib Includes + */ + +#include "pkglib.h" +#include "pkgstrct.h" +#include "libintl.h" +#include "pkglocale.h" + +/* + * External definitions + */ + +/* + * Public methods + */ + +/* + * Name: pkgstrRemoveLeadingWhitespace + * Synopsis: Remove leading whitespace from string + * Description: Remove all leading whitespace characters from a string + * Arguments: a_str - [RO, *RW] - (char **) + * Pointer to handle to string (in allocated storage) to + * remove all leading whitespace from + * Returns: void + * The input string is modified as follows: + * == (char *)NULL: + * - input string was (char *)NULL + * - input string is all whitespace + * != (char *)NULL: + * - copy of input string with leading + * whitespace removed + * CAUTION: The input string must be allocated space (via mem* or + * pkgstr* methods) - it must not be a static or inline + * character string + * NOTE: The input string a_str will be freed with 'free' + * if it is all whitespace, or if it contains any leading + * whitespace characters + * NOTE: Any string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the string is no longer needed. + * Errors: If the string cannot be created, the process exits + */ + +void +pkgstrRemoveLeadingWhitespace(char **a_str) +{ + char *o_str; + + /* entry assertions */ + + assert(a_str != (char **)NULL); + + /* if string is null, just return */ + + if (*a_str == (char *)NULL) { + return; + } + o_str = *a_str; + + /* if string is empty, deallocate and return NULL */ + + if (*o_str == '\0') { + /* free string - handle is reset to NULL by free */ + free(*a_str); + *a_str = (char *)NULL; + return; + } + + /* if first character is not a space, just return */ + + if (!isspace(*o_str)) { + return; + } + + /* advance past all space characters */ + + while ((*o_str != '\0') && (isspace(*o_str))) { + o_str++; + } + + /* if string was all space characters, deallocate and return NULL */ + + if (*o_str == '\0') { + /* free string - *a_str is reset to NULL by free */ + free(*a_str); + *a_str = (char *)NULL; + return; + } + + /* have non-space/null byte, return dup, deallocate original */ + + o_str = strdup(o_str); + assert(o_str != (char *)NULL); + if (o_str != (char *)NULL) { + free(*a_str); + *a_str = o_str; + } +} + +unsigned long +pkgstrNumTokens(char *a_string, char *a_separators) +{ + int index; + + if (a_string == (char *)NULL) { + return (0); + } + + if (*a_string == '\0') { + return (0); + } + + for (index = 0 ; ; index ++) { + char *p; + + p = pkgstrGetToken((char *)NULL, a_string, index, a_separators); + if (p == (char *)NULL) { + return (index); + } + free(p); + } +} + +/* + * Name: pkgstrPrintf_r + * Synopsis: Create string from printf style format and arguments + * Description: Call to convert a printf style format and arguments into a + * string of characters placed in allocated storage + * Arguments: a_buf - [RO, *RW] - (char *) + * - Pointer to buffer used as storage space for the + * returned string created + * a_bufLen - [RO, *RO] - (int) + * - Size of 'a_buf' in bytes - a maximum of 'a_bufLen-1' + * bytes will be placed in 'a_buf' - the returned + * string is always null terminated + * a_format - [RO, RO*] (char *) + * printf-style format for string to be formatted + * VARG_LIST - [RO] (?) + * arguments as appropriate to 'format' specified + * Returns: void + */ + +/*PRINTFLIKE3*/ +void +pkgstrPrintf_r(char *a_buf, int a_bufLen, char *a_format, ...) +{ + va_list ap; + size_t vres = 0; + + /* entry assertions */ + + assert(a_format != (char *)NULL); + assert(*a_format != '\0'); + assert(a_buf != (char *)NULL); + assert(a_bufLen > 1); + + /* generate the results of the printf conversion */ + + va_start(ap, a_format); + vres = vsnprintf(a_buf, a_bufLen-1, a_format, ap); + va_end(ap); + + assert(vres > 0); + assert(vres < a_bufLen); + + a_buf[a_bufLen-1] = '\0'; +} + +/* + * Name: pkgstrPrintf + * Synopsis: Create string from printf style format and arguments + * Description: Call to convert a printf style format and arguments into a + * string of characters placed in allocated storage + * Arguments: format - [RO, RO*] (char *) + * printf-style format for string to be formatted + * VARG_LIST - [RO] (?) + * arguments as appropriate to 'format' specified + * Returns: char * + * A string representing the printf conversion results + * NOTE: Any string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the string is no longer needed. + * Errors: If the string cannot be created, the process exits + */ + +/*PRINTFLIKE1*/ +char * +pkgstrPrintf(char *a_format, ...) +{ + va_list ap; + size_t vres = 0; + char bfr[1]; + char *rstr = (char *)NULL; + + /* entry assertions */ + + assert(a_format != (char *)NULL); + assert(*a_format != '\0'); + + /* determine size of the message in bytes */ + + va_start(ap, a_format); + vres = vsnprintf(bfr, 1, a_format, ap); + va_end(ap); + + assert(vres > 0); + assert(vres < LINE_MAX); + + /* allocate storage to hold the message */ + + rstr = (char *)calloc(1, vres+2); + assert(rstr != (char *)NULL); + if (rstr == (char *)NULL) { + return ((char *)NULL); + } + + /* generate the results of the printf conversion */ + + va_start(ap, a_format); + vres = vsnprintf(rstr, vres+1, a_format, ap); + va_end(ap); + + assert(vres > 0); + assert(vres < LINE_MAX); + assert(*rstr != '\0'); + + /* return the results */ + + return (rstr); +} + +/* + * Name: pkgstrExpandTokens + * Synopsis: Expand tokens from string appending tokens to another + * Description: Given a string and a list of one or more separators, + * expand each token from the string and append those tokens + * to a string that is in allocated space - create new string + * if no string to append to exists. + * Arguments: a_old - [RO, *RW] - (char **) + * - Pointer to handle to string to append token to + * == (char *)NULL - new string is created + * a_separator - [RO, *RO] - (char *) + * - separator to end tokens returned + * a_separators - [RO, *RO] - (char *) + * - String containing one or more characters that + * can separate one "token" from a_string from another + * Returns: void + * NOTE: Any token string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the token string is no longer needed. + */ + +void +pkgstrExpandTokens(char **a_old, char *a_string, char a_separator, + char *a_separators) +{ + int i; + char sep[2] = {'\0', '\0'}; + + /* convert single separator character into character string */ + + sep[0] = a_separator; + + /* + * iterate extracting tokens from the source string and adding + * those tokens to the target string when the tokens are not + * already present in the target string + */ + + for (i = 0; ; i++) { + char *p; + + /* extract the next matching token from the source string */ + + p = pkgstrGetToken((char *)NULL, a_string, i, a_separators); + + /* return if no token is available */ + + if (p == (char *)NULL) { + return; + } + + /* + * obtained token from source string: if the token is not + * in the target string, add the token to the target string + */ + + if (pkgstrContainsToken(*a_old, p, sep) == B_FALSE) { + pkgstrAddToken(a_old, p, *sep); + } + + /* free up temporary storage used by token from source string */ + + free(p); + } + /*NOTREACHED*/ +} + + +/* + * Name: pkgstrGetToken + * Synopsis: Get a separator delimited token from a string + * Description: Given a string and a list of one or more separators, + * return the position specified token (sequence of one or + * more characters that do not include any of the separators) + * Arguments: r_sep - [*RW] - (char *) + * - separator that ended the token returned + * - NOTE: this is a pointer to a "char", e.g.: + * - char a; + * - pkgstrGetToken(&a, ...) + * a_string - [RO, *RO] - (char *) + * - pointer to string to extract token from + * a_index - [RO, *RO] - (int) + * - Index of token to return; '0' is first matching + * token, '1' is second matching token, etc. + * a_separators - [RO, *RO] - (char *) + * - String containing one or more characters that + * can separate one "token" from another + * Returns: char * + * == (char *)NULL - no token matching criteria found + * != (char *)NULL - token matching criteria + * NOTE: Any token string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the token string is no longer needed. + */ + +char * +pkgstrGetToken(char *r_sep, char *a_string, int a_index, char *a_separators) +{ + char *p; + char *q; + char *lasts; + + /* entry assertions */ + + assert(a_string != (char *)NULL); + assert(a_index >= 0); + assert(a_separators != (char *)NULL); + assert(*a_separators != '\0'); + + /* if returned separator requested, reset to null until token found */ + + if (r_sep != (char *)NULL) { + *r_sep = '\0'; + } + + /* duplicate original string before breaking down into tokens */ + + p = strdup(a_string); + assert(p != (char *)NULL); + if (p == (char *)NULL) { + return ((char *)NULL); + } + lasts = p; + + /* scan for separators and return 'index'th token found */ + + while (q = strtok_r((char *)NULL, a_separators, &lasts)) { + /* retrieve separator if requested */ + + if (r_sep != (char *)NULL) { + char *x; + + x = strpbrk(a_string, a_separators); + if (x) { + *r_sep = *x; + } + } + + /* if this is the 'index'th token requested return it */ + + if (a_index-- == 0) { + char *tmp; + + /* duplicate token into its own storage */ + + tmp = strdup(q); + assert(tmp != (char *)NULL); + if (tmp == (char *)NULL) { + return ((char *)NULL); + } + + /* free up copy of original input string */ + + free(p); + + /* return token found */ + + return (tmp); + } + } + + /* + * token not found + */ + + /* free up copy of original input string */ + + free(p); + + /* return NULL pointer (token not found) */ + + return ((char *)NULL); +} + +/* + * Name: pkgstrGetToken + * Synopsis: Get separator delimited token from a string into a fixed buffer + * Description: Given a string and a list of one or more separators, + * return the position specified token (sequence of one or + * more characters that do not include any of the separators) + * into a specified buffer of a fixed maximum size + * Arguments: r_sep - [*RW] - (char *) + * - separator that ended the token returned + * - NOTE: this is a pointer to a "char", e.g.: + * - char a; + * - pkgstrGetToken(&a, ...) + * a_string - [RO, *RO] - (char *) + * - pointer to string to extract token from + * a_index - [RO, *RO] - (int) + * - Index of token to return; '0' is first matching + * token, '1' is second matching token, etc. + * a_separators - [RO, *RO] - (char *) + * - String containing one or more characters that + * can separate one "token" from another + * a_buf - [RO, *RW] - (char *) + * - Pointer to buffer used as storage space for the + * returned token - the returned token is always + * null terminated + * a_buf[0] == '\0' - no token meeting criteria found + * a_buf[0] != '\0' - token meeting criteria returned + * a_bufLen - [RO, *RO] - (int) + * - Size of 'a_buf' in bytes - a maximum of 'a_bufLen-1' + * bytes will be placed in 'a_buf' - the returned + * token is always null terminated + * Returns: void + */ + +void +pkgstrGetToken_r(char *r_sep, char *a_string, int a_index, + char *a_separators, char *a_buf, int a_bufLen) +{ + char *p; + char *q; + char *lasts; + + /* entry assertions */ + + assert(a_string != (char *)NULL); + assert(a_index >= 0); + assert(a_separators != (char *)NULL); + assert(*a_separators != '\0'); + assert(a_buf != (char *)NULL); + assert(a_bufLen > 0); + + /* reset returned separator */ + + if (r_sep != (char *)NULL) { + *r_sep = '\0'; + } + + /* zero out contents of return buffer */ + + bzero(a_buf, a_bufLen); + + /* duplicate original string before breaking down into tokens */ + + p = strdup(a_string); + assert(p != (char *)NULL); + if (p == (char *)NULL) { + return; + } + lasts = p; + + /* scan for separators and return 'index'th token found */ + + while (q = strtok_r((char *)NULL, a_separators, &lasts)) { + /* retrieve separator if requested */ + + if (r_sep != (char *)NULL) { + char *x; + x = strpbrk(a_string, a_separators); + if (x) { + *r_sep = *x; + } + } + + /* if this is the 'index'th token requested return it */ + + if (a_index-- == 0) { + /* copy as many characters as possible to return buf */ + + (void) strncpy(a_buf, q, a_bufLen-1); + break; + } + } + + /* free up copy of original input string */ + + free(p); +} + +/* + * Name: pkgstrAddToken + * Synopsis: Add a token to a string + * Description: Append a token (sequence of one or more characters) to a + * string that is in allocated space - create new string if + * no string to append to exists + * Arguments: a_old - [RO, *RW] - (char **) + * - Pointer to handle to string to append token to + * == (char *)NULL - new string is created + * a_new - [RO, *RO] - (char *) + * - Pointer to string representing token to append + * to the end of the "a_old" string + * == (char *)NULL - no action is performed + * a_new[0] == '\0' - no action is performed + * a_separator - [RO, *RO] - (char) + * - One character placed between the old (existing) + * string and the new token to be added IF the old + * string exists and is not empty (zero length) + * Returns: void + * CAUTION: The old (existing) string must be allocated space (via lu_mem* + * or pkgstr* methods) - it must not be a static or inline + * character string + * NOTE: The old (existing) string may be freed with 'free' + * if a token is appended to it + * NOTE: Any string returned in 'a_old' is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the token string is no longer needed. + */ + +void +pkgstrAddToken(char **a_old, char *a_new, char a_separator) +{ + /* entry assertions */ + + assert(a_old != (char **)NULL); + assert(a_separator != '\0'); + + /* if token to add is null, just return */ + + if (a_new == (char *)NULL) { + return; + } + + /* if token to add is empty (zero length), just return */ + + if (*a_new == '\0') { + return; + } + + /* make sure that new token does not contain the separator */ + + assert(strchr(a_new, (int)a_separator) == (char *)NULL); + + /* if old string is empty (zero length), deallocate */ + + if ((*a_old != (char *)NULL) && ((*a_old)[0] == '\0')) { + /* *a_old is set to NULL by free */ + free(*a_old); + *a_old = (char *)NULL; + } + + /* if old string is exists, append separator and token */ + + if (*a_old != (char *)NULL) { + char *p; + p = pkgstrPrintf("%s%c%s", *a_old, a_separator, a_new); + free(*a_old); + *a_old = p; + return; + } + + /* old string does not exist - return duplicate of token */ + + assert(*a_old == (char *)NULL); + *a_old = strdup(a_new); + assert(*a_old != (char *)NULL); +} + +/* + * Name: pkgstrContainsToken + * Synopsis: Does a given string contain a specified substring + * Description: Determine if a given substring exists in a larger string + * Arguments: a_string - [RO, *RO] - (char *) + * Pointer to string to look for substring in + * a_token - [RO, *RO] - (char *) + * Pointer to substring to look for in larger string + * Results: boolean_t + * B_TRUE - substring exists in larger string + * B_FALSE - substring does NOT exist in larger string + * NOTE: The substring must match on a "token" basis; that is, the + * substring must exist in the larger string delineated with + * either spaces or tabs to match. + */ + +boolean_t +pkgstrContainsToken(char *a_string, char *a_token, char *a_separators) +{ + char *lasts; + char *current; + char *p; + + /* entry assertions */ + + assert(a_separators != (char *)NULL); + assert(*a_separators != '\0'); + + /* if token is not supplied, return false */ + + if (a_token == (char *)NULL) { + return (B_FALSE); + } + + /* if no string provided, return false */ + + if (a_string == (char *)NULL) { + return (B_FALSE); + } + + /* if string empty (zero length), return false */ + + if (*a_string == '\0') { + return (B_FALSE); + } + + /* duplicate larger string because strtok_r changes it */ + + p = strdup(a_string); + assert(p != (char *)NULL); + if (p == (char *)NULL) { + return (B_FALSE); + } + + lasts = p; + + /* scan each token looking for a match */ + + while ((current = strtok_r((char *)NULL, a_separators, &lasts)) != + (char *)NULL) { + if (streq(current, a_token)) { + free(p); + return (B_TRUE); + } + } + + /* free up temporary storage */ + + free(p); + + /* not found */ + + return (B_FALSE); +} + +/* + * Name: pkgstrRemoveToken + * Synopsis: Remove a token from a string + * Description: Remove a token (sequence of one or more characters) from a + * string that is in allocated space + * Arguments: r_string - [RO, *RW] - (char **) + * - Pointer to handle to string to remove token from + * a_token - [RO, *RO] - (char *) + * Pointer to token (substring) to look for and remove + * from r_string provided + * a_separators - [RO, *RO] - (char *) + * - String containing one or more characters that + * separate one "token" from another in r_string + * a_index - [RO, *RO] - (int) + * - Index of token to remove; '0' is first matching + * token, '1' is second matching token, etc. + * Returns: void + * CAUTION: The input string must be allocated space (via lu_mem* or + * pkgstr* methods) - it must not be a static or inline + * character string + * NOTE: The input string r_string will be freed with 'free' + * if the token to be removed is found + * NOTE: Any token string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the token string is no longer needed. + * Errors: If the new token string cannot be created, the process exits + */ + +void +pkgstrRemoveToken(char **r_string, char *a_token, char *a_separators, + int a_index) +{ + char *a_string; + char *copyString; + char sep = 0; + int copyLength; + int i; + + /* entry assertions */ + + assert(r_string != (char **)NULL); + assert(a_token != (char *)NULL); + assert(*a_token != '\0'); + assert(a_separators != (char *)NULL); + assert(*a_separators != '\0'); + + /* simple case: input string is null; return empty string */ + + a_string = *r_string; + if (*a_string == '\0') { + return; + } + + /* simple case: token == input string; return empty string */ + + if (streq(a_string, a_token)) { + /* deallocate input string; free sets *r_string to NULL */ + + free(*r_string); + *r_string = (char *)NULL; + return; + } + + /* simple case: token not in input string: return */ + + if (!pkgstrContainsToken(a_string, a_token, a_separators)) { + return; + } + + /* + * Pick apart the old string building the new one as we go along + * removing the first occurance of the token provided + */ + + copyLength = (strlen(a_string)-strlen(a_token))+2; + copyString = calloc(1, copyLength); + assert(copyString != (char *)NULL); + if (copyString == (char *)NULL) { + return; + } + + for (i = 0; ; i++) { + char *p; + + p = pkgstrGetToken(&sep, a_string, i, a_separators); + if (p == (char *)NULL) { + break; + } + + if (streq(p, a_token) && (a_index-- == 0)) { + continue; + } + + if (*copyString) { + assert(sep != '\0'); + (void) strncat(copyString, &sep, 1); + } + + (void) strcat(copyString, p); + } + + free(*r_string); + assert(*copyString); + *r_string = copyString; +} + +/* + * Name: pkgstrScaleNumericString + * Synopsis: Convert unsigned long long to human readable form + * Description: Convert a string containing an unsigned long long representation + * and convert it into a human readable numeric string. The number + * is scaled down until it is small enough to be in a good human + * readable format i.e. in the range 0 thru scale-1. + * Arguments: a_buf - [RO, *RW] - (char *) + * Pointer to buffer containing string representation + * of unsigned long long to convert + * scale - [RO, *RO] - (unsigned long long) + * Value to scale the number into + * Returns: a_buf - contains human readable scaled representation of + * original value contained in the buffer + * Note: The value "(unsigned long long)-1" is a special case and + * is always converted to "-1". + * Errors: If the string cannot be created, the process exits + */ + +void +pkgstrScaleNumericString(char *a_buf, unsigned long long scale) +{ +static char *M = " KMGTPE"; /* Measurement: */ + /* kilo, mega, giga, tera, peta, exa */ + + unsigned long long number = 0; /* convert this number */ + unsigned long long save = 0; + char *uom = M; /* unit of measurement, initially ' ' (=M[0]) */ + + /* entry assertions */ + + assert(scale > (unsigned long long)0); + assert(scale <= (unsigned long long)1048576); + + /* + * Get the number - if no number of empty number, just return + */ + + if (a_buf == (char *)NULL) { + return; + } + + if (*a_buf == '\0') { + (void) strcpy(a_buf, "0"); + return; + } + + /* convert out the number from the input buffer */ + + number = strtoull(a_buf, (char **)NULL, 10); + + /* if conversion error, return "-1" */ + + if ((long long)number == (long long)-1) { + (void) strcpy(a_buf, "-1"); + return; + } + + /* + * Now have number as a count of scale units. + * Stop scaling when we reached exa-bytes, then something is + * probably wrong with our number (it is improbably large) + */ + + while ((number >= scale) && (*uom != 'E')) { + uom++; /* next unit of measurement */ + save = number; + number = (number + (scale / 2)) / scale; + } + + /* check if we should output a decimal place after the point */ + + if (save && ((save / scale) < 10)) { + /* sprintf() will round for us */ + float fnum = (float)save / scale; + (void) sprintf(a_buf, "%4.1f%c", fnum, *uom); + } else { + (void) sprintf(a_buf, "%4llu%c", number, *uom); + } +} + +/* + * Name: pkgstrLocatePathBasename + * Synopsis: Locate position of base name in path string + * Description: Locate the base name (last path item) in a path and + * return a pointer to the first byte of the base name + * within the given path + * Arguments: a_path - [RO, *RO] - (char *) + * - Pointer to string representing path to scan + * Returns: char * + * - Pointer into string of first byte of path base name + * - == (char *)NULL - input path is (char *)NULL + */ + +char * +pkgstrLocatePathBasename(char *a_path) +{ + char *p; + + /* if path is NULL, return NULL */ + + if (!a_path) { + return (a_path); + } + + /* locate last occurance of '/' in path */ + + p = strrchr(a_path, '/'); + if (p != (char *)NULL) { + /* base name located - return -> first byte */ + return (p+1); + } + + /* no occurance of '/' - entry path must be basename */ + + return (a_path); +} + +/* + * Name: pkgstrConvertPathToBasename + * Synopsis: Return copy of base name in path string + * Description: Locate the base name (last path item) in a path and + * return a copy of the base name in allocated storage + * Arguments: a_path - [RO, *RO] - (char *) + * - Pointer to string representing path to scan + * Returns: char * + * - String containing path base name + * - == (char *)NULL - input path is (char *)NULL + * NOTE: Any string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the string is no longer needed. + * Errors: If the string cannot be created, the process exits + */ + +char * +pkgstrConvertPathToBasename(char *a_path) +{ + char *p; + + /* if path is NULL, return NULL */ + + if (a_path == (char *)NULL) { + return ((char *)NULL); + } + + /* if path is empty (zero length), return NULL */ + + if (*a_path == '\0') { + return ((char *)NULL); + } + + /* locate last occurance of '/' in path */ + + p = strrchr(a_path, '/'); + if (p == (char *)NULL) { + /* no occurance of '/' - entry path must be basename */ + + return (strdup(a_path)); + } + + /* base name located - return string from -> first byte */ + + return (strdup(p+1)); +} + +/* + * Name: pkgstrConvertPathToDirname + * Synopsis: Return copy of directory in path string + * Description: Locate the directory name (everything but last path item) in a + * path and return a copy of the dir name in allocated storage + * Arguments: a_path - [RO, *RO] - (char *) + * - Pointer to string representing path to scan + * Returns: char * + * - String containing path directory name + * - == (char *)NULL - input path is (char *)NULL, + * or a_path is empty (*a_path == '\0'), or the + * a_path has no directory name in it. + * NOTE: Any string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the string is no longer needed. + * Errors: If the string cannot be created, the process exits + */ + +char * +pkgstrConvertPathToDirname(char *a_path) +{ + char *p; + char *retPath; + + /* if path is NULL, return NULL */ + + if (a_path == (char *)NULL) { + return ((char *)NULL); + } + + /* if path is empty (zero length), return NULL */ + + if (*a_path == '\0') { + return ((char *)NULL); + } + + /* locate last occurance of '/' in path */ + + p = strrchr(a_path, '/'); + if (p == (char *)NULL) { + /* no occurance of '/' - entire path must be basename */ + + return ((char *)NULL); + } + + /* duplicate original path */ + + retPath = strdup(a_path); + assert(retPath != (char *)NULL); + if (retPath == (char *)NULL) { + return ((char *)NULL); + } + + /* remove all trailing '/'s from copy of path */ + + for (p = strrchr(retPath, '/'); (p > retPath) && (*p == '/'); p--) { + *p = '\0'; + } + + /* if entire path was '/'s, return null string - no directory present */ + + if (*retPath == '\0') { + free(retPath); + return ((char *)NULL); + } + + /* path has at least one non-'/' in it - return -> directory portion */ + + return (retPath); +} + +/* + * Name: pkgstrConvertUllToTimeString_r + * Synopsis: Convert an unsigned long long into a "time string" + * Description: Given an unsigned long long, return a "time string" which is a + * conversion of the unsigned long long interpreted as a number of + * nanoseconds into a "hour:minute:second.ns" ascii string + * Arguments: a_time - [RO, *RO] - (unsigned long long)n + * - value to convert + * a_buf - [RO, *RW] - (char *) + * - Pointer to buffer used as storage space for the + * returned string + * a_bufLen - [RO, *RO] - (int) + * - Size of 'a_buf' in bytes - a maximum of 'a_bufLen-1' + * bytes will be placed in 'a_buf' + * Returns: char * + * - String containing converted value + * NOTE: Any string returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the string is no longer needed. + * Errors: If the string cannot be created, the process exits + */ + +void +pkgstrConvertUllToTimeString_r(unsigned long long a_time, + char *a_buf, int a_bufLen) +{ + unsigned long long seconds; + unsigned long long minutes; + unsigned long long hours; + unsigned long long ns; + + /* entry assertions */ + + assert(a_buf != (char *)NULL); + assert(a_bufLen > 0); + + /* if time is 0, return immediate result */ + + if (a_time == 0) { + pkgstrPrintf_r(a_buf, a_bufLen, "%s", "0:00:00.000000000"); + return; + } + + /* break out individual time components */ + + ns = a_time % 1000000000ll; /* nanoseconds left over from seconds */ + seconds = a_time / 1000000000ll; /* total seconds */ + minutes = seconds / 60ll; /* total minutes */ + seconds = seconds % 60ll; /* seconds left over from minutes */ + hours = minutes / 60ll; /* total hours */ + minutes = minutes % 60ll; /* minutes left over from hours */ + + /* return a converted string */ + + pkgstrPrintf_r(a_buf, a_bufLen, "%llu:%02llu:%02llu.%09llu", + hours, minutes, seconds, ns); +} diff --git a/usr/src/lib/libpkg/common/pkgtrans.c b/usr/src/lib/libpkg/common/pkgtrans.c new file mode 100644 index 0000000000..dbaaa73adf --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgtrans.c @@ -0,0 +1,1973 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <errno.h> +#include <stdarg.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/sysmacros.h> +#include <dirent.h> +#include <signal.h> +#include <devmgmt.h> +#include <openssl/pkcs12.h> +#include <openssl/x509.h> +#include <openssl/pkcs7.h> +#include <openssl/err.h> +#include <openssl/pem.h> +#include "pkginfo.h" +#include "pkgstrct.h" +#include "pkgtrans.h" +#include "pkgdev.h" +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "keystore.h" +#include "pkglocale.h" +#include "pkgerr.h" + +extern char *pkgdir; /* pkgparam.c */ + +/* libadm.a */ +extern char *devattr(char *device, char *attribute); +extern char *fpkginst(char *pkg, ...); +extern int fpkginfo(struct pkginfo *info, char *pkginst); +extern int getvol(char *device, char *label, int options, char *prompt); +extern int _getvol(char *device, char *label, int options, char *prompt, + char *norewind); + +/* dstream.c */ +extern int ds_ginit(char *device); +extern int ds_close(int pkgendflg); + +#define CPIOPROC "/usr/bin/cpio" + +#define CMDSIZE 512 /* command block size */ + +#define BLK_SIZE 512 /* size of logical block */ + +#define ENTRY_MAX 256 /* max size of entry for cpio cmd or header */ + +#define PKGINFO "pkginfo" +#define PKGMAP "pkgmap" +#define MAP_STAT_SIZE 60 /* 1st line of pkgmap (3 numbers & a : */ + +#define INSTALL "install" +#define RELOC "reloc" +#define ROOT "root" +#define ARCHIVE "archive" + +static struct pkgdev srcdev, dstdev; +static char *tmpdir; +static char *tmppath; +static char *tmpsymdir = NULL; +static char dstinst[NON_ABI_NAMELNGTH]; +static char *ids_name, *ods_name; +static int ds_volcnt; +static int ds_volno; +static int compressedsize, has_comp_size; + +static void (*sigintHandler)(); +static void (*sighupHandler)(); +static void cleanup(void); +static void sigtrap(int signo); +static int rd_map_size(FILE *fp, int *npts, int *maxpsz, int *cmpsize); + +static int cat_and_count(struct dm_buf *, char *); + +static int ckoverwrite(char *dir, char *inst, int options); +static int pkgxfer(char *srcinst, int options); +static int wdsheader(struct dm_buf *, char *src, char *device, + char **pkg, PKCS7 *); +static struct dm_buf *genheader(char *, char *, char **); + +static int dump_hdr_and_pkgs(BIO *, struct dm_buf *, char **); + +extern int ds_fd; /* open file descriptor for data stream WHERE? */ + +static char *root_names[] = { + "root", + "root.cpio", + "root.Z", + "root.cpio.Z", + 0 +}; + +static char *reloc_names[] = { + "reloc", + "reloc.cpio", + "reloc.Z", + "reloc.cpio.Z", + 0 +}; + +static int signal_received = 0; + +char **xpkg; /* array of transferred packages */ +int nxpkg; + +static char *allpkg[] = { + "all", + NULL +}; + +static struct dm_buf hdrbuf; +static char *pinput, *nextpinput; + +int +pkghead(char *device) +{ + char *pt; + int n; + + cleanup(); + + + if (device == NULL) + return (0); + else if ((device[0] == '/') && !isdir(device)) { + pkgdir = device; + return (0); + } else if ((pt = devattr(device, "pathname")) != NULL && !isdir(pt)) { + pkgdir = pt; + return (0); + } + + /* check for datastream */ + if (n = pkgtrans(device, (char *)0, allpkg, PT_SILENT|PT_INFO_ONLY, + NULL, NULL)) { + cleanup(); + return (n); + } + /* pkgtrans has set pkgdir */ + return (0); +} + +static char * +mgets(char *buf, int size) +{ + nextpinput = strchr(pinput, '\n'); + if (nextpinput == NULL) + return (0); + *nextpinput = '\0'; + if ((int)strlen(pinput) > size) + return (0); + (void) strncpy(buf, pinput, strlen(pinput)); + buf[strlen(pinput)] = '\0'; + pinput = nextpinput + 1; + return (buf); +} +/* + * Here we construct the package size summaries for the headers. The + * pkgmap file associated with fp must be rewound to the beginning of the + * file. Note that we read three values from pkgmap first line in order + * to get the *actual* size if this package is compressed. + * This returns + * 0 : error + * 2 : not a compressed package + * 3 : compressed package + * and sets has_comp_size to indicate whether or not this is a compressed + * package. + */ +static int +rd_map_size(FILE *fp, int *npts, int *maxpsz, int *cmpsize) +{ + int n; + char line_buffer[MAP_STAT_SIZE]; + + /* First read the null terminated first line */ + if (fgets(line_buffer, MAP_STAT_SIZE, fp) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOSIZE)); + (void) fclose(fp); + ecleanup(); + return (0); + } + + n = sscanf(line_buffer, ": %d %d %d", npts, maxpsz, cmpsize); + + if (n == 3) /* A valid compressed package entry */ + has_comp_size = 1; + else if (n == 2) /* A valid standard package entry */ + has_comp_size = 0; + else { /* invalid entry */ + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOSIZE)); + (void) fclose(fp); + ecleanup(); + return (0); + } + + return (n); +} + +/* will return 0, 1, 3, or 99 */ +static int +_pkgtrans(char *device1, char *device2, char **pkg, int options, + keystore_handle_t keystore, char *keystore_alias) +{ + BIO *p7_bio = NULL; + EVP_PKEY *privkey = NULL; + PKCS7 *sec_pkcs7 = NULL; + PKCS7_SIGNER_INFO *sec_signerinfo = NULL; + PKG_ERR *err; + STACK_OF(X509) *cacerts = NULL; + STACK_OF(X509) *clcerts = NULL; + STACK_OF(X509) *sec_chain = NULL; + X509 *pubcert = NULL; + boolean_t making_sig = B_FALSE; + char *src, *dst; + int errflg, i, n; + struct dm_buf *hdr; + + making_sig = (keystore != NULL) ? B_TRUE : B_FALSE; + + if (making_sig) { + + /* new error object */ + err = pkgerr_new(); + + /* find matching cert and key */ + if (find_key_cert_pair(err, keystore, + keystore_alias, &privkey, &pubcert) != 0) { + pkgerr(err); + pkgerr_free(err); + return (1); + } + + /* get CA certificates */ + if (find_ca_certs(err, keystore, &cacerts) != 0) { + pkgerr(err); + pkgerr_free(err); + return (1); + } + + /* get CL (aka "chain") certificates */ + if (find_cl_certs(err, keystore, &clcerts) != 0) { + pkgerr(err); + pkgerr_free(err); + return (1); + } + + /* initialize PKCS7 object to be filled in later */ + sec_pkcs7 = PKCS7_new(); + PKCS7_set_type(sec_pkcs7, NID_pkcs7_signed); + sec_signerinfo = PKCS7_add_signature(sec_pkcs7, + pubcert, privkey, EVP_sha1()); + + if (sec_signerinfo == NULL) { + progerr(gettext(ERR_SEC), keystore_alias); + ERR_print_errors_fp(stderr); + pkgerr_free(err); + return (1); + } + + /* add signer cert into signature */ + PKCS7_add_certificate(sec_pkcs7, pubcert); + + /* attempt to resolve cert chain starting at the signer cert */ + if (get_cert_chain(err, pubcert, clcerts, cacerts, + &sec_chain) != 0) { + pkgerr(err); + pkgerr_free(err); + return (1); + } + + /* + * add the verification chain of certs into the signature. + * The first cert is the user cert, which we don't need, + * since it's baked in already, so skip it + */ + for (i = 1; i < sk_X509_num(sec_chain); i++) { + PKCS7_add_certificate(sec_pkcs7, + sk_X509_value(sec_chain, i)); + } + + pkgerr_free(err); + err = NULL; + } + + if (signal_received > 0) { + return (1); + } + + /* transfer spool to appropriate device */ + if (devtype(device1, &srcdev)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_BADDEV), device1); + return (1); + } + srcdev.rdonly++; + + /* check for datastream */ + ids_name = NULL; + if (srcdev.bdevice) { + if (n = _getvol(srcdev.bdevice, NULL, NULL, + pkg_gt("Insert %v into %p."), srcdev.norewind)) { + cleanup(); + if (n == 3) + return (3); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_GETVOL)); + return (1); + } + if (ds_readbuf(srcdev.cdevice)) + ids_name = srcdev.cdevice; + } + + if (srcdev.cdevice && !srcdev.bdevice) + ids_name = srcdev.cdevice; + else if (srcdev.pathname) { + ids_name = srcdev.pathname; + if (access(ids_name, 0) == -1) { + progerr(ERR_TRANSFER); + logerr(pkg_gt(MSG_GETVOL)); + return (1); + } + } + + if (!ids_name && device2 == (char *)0) { + if (n = pkgmount(&srcdev, NULL, 1, 0, 0)) { + cleanup(); + return (n); + } + if (srcdev.mount && *srcdev.mount) + pkgdir = strdup(srcdev.mount); + return (0); + } + + if (ids_name && device2 == (char *)0) { + tmppath = tmpnam(NULL); + tmppath = strdup(tmppath); + if (tmppath == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + return (1); + } + if (mkdir(tmppath, 0755)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MKDIR), tmppath); + return (1); + } + device2 = tmppath; + } + + if (devtype(device2, &dstdev)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_BADDEV), device2); + return (1); + } + + if ((srcdev.cdevice && dstdev.cdevice) && + strcmp(srcdev.cdevice, dstdev.cdevice) == 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SAMEDEV)); + return (1); + } + + ods_name = NULL; + if (dstdev.cdevice && !dstdev.bdevice || dstdev.pathname) + options |= PT_ODTSTREAM; + + if (options & PT_ODTSTREAM) { + if (!((ods_name = dstdev.cdevice) != NULL || + (ods_name = dstdev.pathname) != NULL)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_BADDEV), device2); + return (1); + } + if (ids_name) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_TWODSTREAM)); + return (1); + } + } else { + /* + * output device isn't a stream. If we're making a signed + * package, then fail, since we can't make signed, + * non-stream pkgs + */ + if (making_sig) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(ERR_CANTSIGN)); + return (1); + } + } + + if ((srcdev.dirname && dstdev.dirname) && + strcmp(srcdev.dirname, dstdev.dirname) == 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SAMEDEV)); + return (1); + } + + if ((srcdev.pathname && dstdev.pathname) && + strcmp(srcdev.pathname, dstdev.pathname) == 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SAMEDEV)); + return (1); + } + + if (signal_received > 0) { + return (1); + } + + if (ids_name) { + if (srcdev.cdevice && !srcdev.bdevice && + (n = _getvol(srcdev.cdevice, NULL, NULL, NULL, + srcdev.norewind))) { + cleanup(); + if (n == 3) + return (3); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_GETVOL)); + return (1); + } + if (srcdev.dirname = tmpnam(NULL)) + tmpdir = srcdev.dirname = strdup(srcdev.dirname); + + if ((srcdev.dirname == NULL) || mkdir(srcdev.dirname, 0755) || + chdir(srcdev.dirname)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTEMP), srcdev.dirname); + cleanup(); + return (1); + } + if (ds_init(ids_name, pkg, srcdev.norewind)) { + cleanup(); + return (1); + } + } else if (srcdev.mount) { + if (n = pkgmount(&srcdev, NULL, 1, 0, 0)) { + cleanup(); + return (n); + } + } + + src = srcdev.dirname; + dst = dstdev.dirname; + + if (chdir(src)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), src); + cleanup(); + return (1); + } + + if (signal_received > 0) { + return (1); + } + + xpkg = pkg = gpkglist(src, pkg, NULL); + if (!pkg) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOPKGS), src); + cleanup(); + return (1); + } + + for (nxpkg = 0; pkg[nxpkg]; /* void */) { + nxpkg++; /* count */ + } + + if (ids_name) { + ds_order(pkg); /* order requests */ + } + + if (signal_received > 0) { + return (1); + } + + if (options & PT_ODTSTREAM) { + char line[128]; + + if (!dstdev.pathname && + (n = _getvol(ods_name, NULL, DM_FORMAT, NULL, + dstdev.norewind))) { + cleanup(); + if (n == 3) + return (3); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_GETVOL)); + return (1); + } + if ((hdr = genheader(src, ods_name, pkg)) == NULL) { + cleanup(); + return (1); + } + if (making_sig) { + /* start up signature data stream */ + PKCS7_content_new(sec_pkcs7, NID_pkcs7_data); + PKCS7_set_detached(sec_pkcs7, 1); + p7_bio = PKCS7_dataInit(sec_pkcs7, NULL); + + /* + * Here we generate all the data that will go into + * the package, and send it through the signature + * generator, essentially calculating the signature + * of the entire package so we can place it in the + * header. Otherwise we'd have to place it at the end + * of the pkg, which would break the ABI + */ + if (!(options & PT_SILENT)) { + (void) fprintf(stderr, pkg_gt(MSG_SIGNING), + get_subject_display_name(pubcert)); + } + if (dump_hdr_and_pkgs(p7_bio, hdr, pkg) != 0) { + progerr(gettext(ERR_NOGEN)); + logerr(pkg_gt(MSG_GETVOL)); + cleanup(); + return (1); + + } + + BIO_flush(p7_bio); + + /* + * now generate PKCS7 signature + */ + if (!PKCS7_dataFinal(sec_pkcs7, p7_bio)) { + progerr(gettext(ERR_NOGEN)); + logerr(pkg_gt(MSG_GETVOL)); + cleanup(); + return (1); + } + + BIO_free(p7_bio); + } + + /* write out header to stream, which includes signature */ + if (wdsheader(hdr, src, ods_name, pkg, sec_pkcs7)) { + cleanup(); + return (1); + } + + if (sec_pkcs7 != NULL) { + /* nuke in-memory signature for safety */ + PKCS7_free(sec_pkcs7); + sec_pkcs7 = NULL; + } + + ds_volno = 1; /* number of volumes in datastream */ + pinput = hdrbuf.text_buffer; + /* skip past first line in header */ + (void) mgets(line, 128); + } + + if (signal_received > 0) { + return (1); + } + + errflg = 0; + + for (i = 0; pkg[i]; i++) { + + if (signal_received > 0) { + return (1); + } + + if (!(options & PT_ODTSTREAM) && dstdev.mount) { + if (n = pkgmount(&dstdev, NULL, 0, 0, 1)) { + cleanup(); + return (n); + } + } + if (errflg = pkgxfer(pkg[i], options)) { + pkg[i] = NULL; + if ((options & PT_ODTSTREAM) || (errflg != 2)) + break; + } else if (strcmp(dstinst, pkg[i])) + pkg[i] = strdup(dstinst); + } + + if (!(options & PT_ODTSTREAM) && dst) { + pkgdir = strdup(dst); + } + + /* + * No cleanup of temporary directories created in this + * function is done here. The calling function must do + * the cleanup. + */ + + return (signal_received > 0 ? 1 : errflg); +} + +int +pkgtrans(char *device1, char *device2, char **pkg, int options, + keystore_handle_t keystore, char *keystore_alias) +{ + int r; + struct sigaction nact; + struct sigaction oact; + + /* + * setup signal handlers for SIGINT and SIGHUP and release hold + */ + + /* hold SIGINT/SIGHUP interrupts */ + + (void) sighold(SIGHUP); + (void) sighold(SIGINT); + + /* hook SIGINT to sigtrap */ + + nact.sa_handler = sigtrap; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + if (sigaction(SIGINT, &nact, &oact) < 0) { + sigintHandler = SIG_DFL; + } else { + sigintHandler = oact.sa_handler; + } + + /* hook SIGHUP to sigtrap */ + + nact.sa_handler = sigtrap; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + if (sigaction(SIGHUP, &nact, &oact) < 0) { + sighupHandler = SIG_DFL; + } else { + sighupHandler = oact.sa_handler; + } + + /* reset signal received count */ + + signal_received = 0; + + /* release hold on signals */ + + (void) sigrelse(SIGHUP); + (void) sigrelse(SIGINT); + + /* + * perform the package translation + */ + + r = _pkgtrans(device1, device2, pkg, options, keystore, keystore_alias); + + /* + * reset signal handlers + */ + + /* hold SIGINT/SIGHUP interrupts */ + + (void) sighold(SIGHUP); + (void) sighold(SIGINT); + + /* reset SIGINT */ + + nact.sa_handler = sigintHandler; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + (void) sigaction(SIGINT, &nact, (struct sigaction *)NULL); + + /* reset SIGHUP */ + + nact.sa_handler = sighupHandler; + nact.sa_flags = SA_RESTART; + (void) sigemptyset(&nact.sa_mask); + + (void) sigaction(SIGHUP, &nact, (struct sigaction *)NULL); + + /* if signal received and pkgtrans returned error, call cleanup */ + + if (signal_received > 0) { + if (r != 0) { + cleanup(); + } + (void) kill(getpid(), SIGINT); + } + + /* release hold on signals */ + + (void) sigrelse(SIGHUP); + (void) sigrelse(SIGINT); + + return (r); +} + +/* + * This function concatenates append to the text described in the buf_ctrl + * structure. This code modifies data in this structure and handles all + * allocation issues. It returns '0' if everything was successful and '1' + * if not. + */ +static int +cat_and_count(struct dm_buf *buf_ctrl, char *append) +{ + + /* keep allocating until we have enough room to hold string */ + while ((buf_ctrl->offset + (int)strlen(append)) + >= buf_ctrl->allocation) { + /* reallocate (and maybe move) text buffer */ + if ((buf_ctrl->text_buffer = + (char *)realloc(buf_ctrl->text_buffer, + buf_ctrl->allocation + BLK_SIZE)) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + free(buf_ctrl->text_buffer); + return (1); + } + + /* clear the new memory */ + (void) memset(buf_ctrl->text_buffer + + buf_ctrl->allocation, '\0', BLK_SIZE); + + /* adjust total allocation */ + buf_ctrl->allocation += BLK_SIZE; + } + + /* append new string to end of buffer */ + while (*append) { + *(buf_ctrl->text_buffer + buf_ctrl->offset) = *append++; + (buf_ctrl->offset)++; + } + + return (0); +} + +static struct dm_buf * +genheader(char *src, char *device, char **pkg) +{ + + FILE *fp; + char path[MAXPATHLEN], tmp_entry[ENTRY_MAX]; + int i, n, nparts, maxpsize; + int partcnt, totsize; + struct stat statbuf; + + if ((hdrbuf.text_buffer = (char *)malloc(BLK_SIZE)) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + return (NULL); + } + + /* clear the new memory */ + (void) memset(hdrbuf.text_buffer, '\0', BLK_SIZE); + + /* set up the buffer control structure for the header */ + hdrbuf.offset = 0; + hdrbuf.allocation = BLK_SIZE; + + (void) cat_and_count(&hdrbuf, HDR_PREFIX); + (void) cat_and_count(&hdrbuf, "\n"); + + nparts = maxpsize = 0; + + totsize = 0; + for (i = 0; pkg[i]; i++) { + (void) snprintf(path, MAXPATHLEN, "%s/%s/%s", + src, pkg[i], PKGINFO); + if (stat(path, &statbuf) < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_BADPKGINFO)); + ecleanup(); + return (NULL); + } + totsize += statbuf.st_size/BLK_SIZE + 1; + } + + /* + * totsize contains number of blocks used by the pkginfo files + */ + totsize += i/4 + 1; + if (dstdev.capacity && totsize > dstdev.capacity) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOSPACE), totsize, dstdev.capacity); + ecleanup(); + return (NULL); + } + + ds_volcnt = 1; + for (i = 0; pkg[i]; i++) { + partcnt = 0; + (void) snprintf(path, MAXPATHLEN, "%s/%s/%s", + src, pkg[i], PKGMAP); + if ((fp = fopen(path, "r")) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOPKGMAP), pkg[i]); + ecleanup(); + return (NULL); + } + + /* Evaluate the first entry in pkgmap */ + n = rd_map_size(fp, &nparts, &maxpsize, &compressedsize); + + if (n == 3) /* It's a compressed package */ + /* The header needs the *real* size */ + maxpsize = compressedsize; + else if (n == 0) /* pkgmap is corrupt */ + return (NULL); + + if (dstdev.capacity && maxpsize > dstdev.capacity) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOSPACE)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } + + /* add pkg name, number of parts and the max part size */ + if (snprintf(tmp_entry, ENTRY_MAX, "%s %d %d", + pkg[i], nparts, maxpsize) >= ENTRY_MAX) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(ERR_MEM)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } + if (cat_and_count(&hdrbuf, tmp_entry)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } + + totsize += nparts * maxpsize; + if (dstdev.capacity && dstdev.capacity < totsize) { + int lastpartcnt = 0; +#if 0 + if (i != 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOSPACE)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } +#endif /* 0 */ + + if (totsize) + totsize -= nparts * maxpsize; + while (partcnt < nparts) { + while (totsize <= dstdev.capacity && + partcnt <= nparts) { + totsize += maxpsize; + partcnt++; + } + /* partcnt == 0 means skip to next volume */ + if (partcnt) + partcnt--; + (void) snprintf(tmp_entry, ENTRY_MAX, + " %d", partcnt - lastpartcnt); + if (cat_and_count(&hdrbuf, tmp_entry)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } + ds_volcnt++; + totsize = 0; + lastpartcnt = partcnt; + } + /* first parts/volume number does not count */ + ds_volcnt--; + } + + if (cat_and_count(&hdrbuf, "\n")) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } + + (void) fclose(fp); + } + + if (cat_and_count(&hdrbuf, HDR_SUFFIX) || + cat_and_count(&hdrbuf, "\n")) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MEM)); + (void) fclose(fp); + ecleanup(); + return (NULL); + } + return (&hdrbuf); +} + +static int +wdsheader(struct dm_buf *hdr, char *src, char *device, char **pkg, PKCS7 *sig) +{ + FILE *fp; + char path[PATH_MAX], tmp_entry[ENTRY_MAX], + tmp_file[L_tmpnam+1]; + char srcpath[PATH_MAX]; + int i, n; + int list_fd; + int block_cnt; + int len; + char cwd[MAXPATHLEN + 1]; + boolean_t making_sig = B_FALSE; + + making_sig = (sig != NULL) ? B_TRUE : B_FALSE; + + (void) ds_close(0); + if (dstdev.pathname) + ds_fd = creat(device, 0644); + else + ds_fd = open(device, 1); + + if (ds_fd < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), device, errno); + return (1); + } + + if (ds_ginit(device) < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), device, errno); + (void) ds_close(0); + return (1); + } + + /* + * The loop below assures compatibility with tapes that don't + * have a block size (e.g.: Exabyte) by forcing EOR at the end + * of each 512 bytes. + */ + for (block_cnt = 0; block_cnt < hdr->allocation; + block_cnt += BLK_SIZE) { + write(ds_fd, (hdr->text_buffer + block_cnt), BLK_SIZE); + } + + /* + * write the first cpio() archive to the datastream + * which should contain the pkginfo & pkgmap files + * for all packages + */ + (void) tmpnam(tmp_file); /* temporary file name */ + if ((list_fd = open(tmp_file, O_RDWR | O_CREAT)) == -1) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTMPFIL)); + return (1); + } + + /* + * Create a cpio-compatible list of the requisite files in + * the temporary file. + */ + if (!making_sig) { + for (i = 0; pkg[i]; i++) { + register ssize_t entry_size; + + /* + * Copy pkginfo and pkgmap filenames into the + * temporary string allowing for the first line + * as a special case. + */ + entry_size = sprintf(tmp_entry, + (i == 0) ? "%s/%s\n%s/%s" : "\n%s/%s\n%s/%s", + pkg[i], PKGINFO, pkg[i], PKGMAP); + + if (write(list_fd, tmp_entry, + entry_size) != entry_size) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTMPFIL)); + (void) close(list_fd); + ecleanup(); + return (1); + } + } + + } else { + register ssize_t entry_size; + + /* + * if we're making a signature, we must make a + * temporary area full of symlinks to the requisite + * files, plus an extra entry for the signature, so + * that cpio will put all files and signature in the + * same archive in a single invocation of cpio. + */ + tmpsymdir = xstrdup(tmpnam(NULL)); + + if (mkdir(tmpsymdir, S_IRWXU)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MKDIR), tmpsymdir); + return (1); + } + + /* generate the signature */ + if (((len = snprintf(path, PATH_MAX, "%s/%s", + tmpsymdir, SIGNATURE_FILENAME)) >= PATH_MAX) || + len < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTMPFIL), tmpsymdir); + cleanup(); + return (1); + } + + if ((fp = fopen(path, "w")) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTMPFIL), path); + cleanup(); + return (1); + } + PEM_write_PKCS7(fp, sig); + (void) fclose(fp); + + for (i = 0; pkg[i]; i++) { + sprintf(path, "%s/%s", tmpsymdir, pkg[i]); + if (mkdir(path, 0755)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MKDIR), path); + cleanup(); + return (1); + } + sprintf(path, "%s/%s/%s", tmpsymdir, + pkg[i], PKGINFO); + sprintf(srcpath, "%s/%s/%s", src, pkg[i], PKGINFO); + if (symlink(srcpath, path) != 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SYMLINK), path, srcpath); + cleanup(); + return (1); + } + + sprintf(path, "%s/%s/%s", tmpsymdir, + pkg[i], PKGMAP); + sprintf(srcpath, "%s/%s/%s", src, pkg[i], PKGMAP); + if (symlink(srcpath, path) != 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SYMLINK), path, srcpath); + cleanup(); + return (1); + } + + /* + * Copy pkginfo and pkgmap filenames into the + * temporary string allowing for the first line + * as a special case. + */ + entry_size = sprintf(tmp_entry, + (i == 0) ? "%s/%s\n%s/%s" : "\n%s/%s\n%s/%s", + pkg[i], PKGINFO, pkg[i], PKGMAP); + + if (write(list_fd, tmp_entry, + entry_size) != entry_size) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTMPFIL)); + (void) close(list_fd); + ecleanup(); + cleanup(); + return (1); + } + } + + /* add signature to list of files */ + entry_size = sprintf(tmp_entry, "\n%s", SIGNATURE_FILENAME); + if (write(list_fd, tmp_entry, entry_size) != entry_size) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOTMPFIL), tmp_file); + (void) close(list_fd); + ecleanup(); + cleanup(); + return (1); + } + } + + (void) lseek(list_fd, 0, SEEK_SET); + + if (!making_sig) { +#ifndef SUNOS41 + (void) sprintf(tmp_entry, "%s -ocD -C %d", + CPIOPROC, (int)BLK_SIZE); +#else + (void) sprintf(tmp_entry, "%s -oc -C %d", + CPIOPROC, (int)BLK_SIZE); +#endif + } else { + /* + * when making a signature, we must make sure to follow + * symlinks during the cpio so that we don't archive + * the links themselves + */ +#ifndef SUNOS41 + (void) sprintf(tmp_entry, "%s -ocDL -C %d", + CPIOPROC, (int)BLK_SIZE); +#else + (void) sprintf(tmp_entry, "%s -ocL -C %d", + CPIOPROC, (int)BLK_SIZE); +#endif + } + + if (making_sig) { + /* save cwd and change to symlink dir for cpio invocation */ + if (getcwd(cwd, MAXPATHLEN + 1) == NULL) { + logerr(pkg_gt(ERR_GETWD)); + progerr(pkg_gt(ERR_TRANSFER)); + cleanup(); + return (1); + } + + if (chdir(tmpsymdir)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), tmpsymdir); + cleanup(); + return (1); + } + } + + if (n = esystem(tmp_entry, list_fd, ds_fd)) { + rpterr(); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CMDFAIL), tmp_entry, n); + (void) close(list_fd); + (void) unlink(tmp_file); + cleanup(); + return (1); + } + + (void) close(list_fd); + (void) unlink(tmp_file); + + if (making_sig) { + /* change to back to src dir for subsequent operations */ + if (chdir(cwd)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), cwd); + cleanup(); + return (1); + } + } + return (0); +} + +static int +ckoverwrite(char *dir, char *inst, int options) +{ + char path[PATH_MAX]; + + (void) sprintf(path, "%s/%s", dir, inst); + if (access(path, 0) == 0) { + if (options & PT_OVERWRITE) + return (rrmdir(path)); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_EXISTS), path); + return (1); + } + return (0); +} + +static int +pkgxfer(char *srcinst, int options) +{ + int r; + struct pkginfo info; + FILE *fp, *pp; + char *pt, *src, *dst; + char dstdir[PATH_MAX], + temp[PATH_MAX], + srcdir[PATH_MAX], + cmd[CMDSIZE], + pkgname[NON_ABI_NAMELNGTH]; + int i, n, part, nparts, maxpartsize, curpartcnt, iscomp; + char volnos[128], tmpvol[128]; + struct statvfs64 svfsb; + longlong_t free_blocks; + struct stat srcstat; + + info.pkginst = NULL; /* required initialization */ + + /* + * when this routine is entered, the first part of + * the package to transfer is already available in + * the directory indicated by 'src' --- unless the + * source device is a datstream, in which case only + * the pkginfo and pkgmap files are available in 'src' + */ + src = srcdev.dirname; + dst = dstdev.dirname; + + if (!(options & PT_SILENT)) + (void) fprintf(stderr, pkg_gt(MSG_TRANSFER), srcinst); + (void) strcpy(dstinst, srcinst); + + if (!(options & PT_ODTSTREAM)) { + /* destination is a (possibly mounted) directory */ + (void) sprintf(dstdir, "%s/%s", dst, dstinst); + + /* + * need to check destination directory to assure + * that we will not be duplicating a package which + * already resides there (though we are allowed to + * overwrite the same version) + */ + pkgdir = src; + if (fpkginfo(&info, srcinst)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOEXISTS), srcinst); + (void) fpkginfo(&info, NULL); + return (1); + } + pkgdir = dst; + + (void) strcpy(temp, srcinst); + if (pt = strchr(temp, '.')) + *pt = '\0'; + (void) strcat(temp, ".*"); + + if (pt = fpkginst(temp, info.arch, info.version)) { + /* + * the same instance already exists, although + * its pkgid might be different + */ + if (options & PT_OVERWRITE) { + (void) strcpy(dstinst, pt); + (void) sprintf(dstdir, "%s/%s", dst, dstinst); + } else { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_DUPVERS), srcinst); + (void) fpkginfo(&info, NULL); + (void) fpkginst(NULL); + return (2); + } + } else if (options & PT_RENAME) { + /* + * find next available instance by appending numbers + * to the package abbreviation until the instance + * does not exist in the destination directory + */ + if (pt = strchr(temp, '.')) + *pt = '\0'; + for (i = 2; (access(dstdir, 0) == 0); i++) { + (void) sprintf(dstinst, "%s.%d", temp, i); + (void) sprintf(dstdir, "%s/%s", dst, dstinst); + } + } else if (options & PT_OVERWRITE) { + /* + * we're allowed to overwrite, but there seems + * to be no valid package to overwrite, and we are + * not allowed to rename the destination, so act + * as if we weren't given permission to overwrite + * --- this keeps us from removing a destination + * instance which is named the same as the source + * instance, but really reflects a different pkg! + */ + options &= (~PT_OVERWRITE); + } + (void) fpkginfo(&info, NULL); + (void) fpkginst(NULL); + + if (ckoverwrite(dst, dstinst, options)) + return (2); + + if (isdir(dstdir) && mkdir(dstdir, 0755)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MKDIR), dstdir); + return (1); + } + + (void) sprintf(srcdir, "%s/%s", src, srcinst); + if (stat(srcdir, &srcstat) != -1) { + if (chmod(dstdir, (srcstat.st_mode & S_IAMB)) == -1) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHMODDIR), dstdir); + return (1); + } + } else { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_STATDIR), srcdir); + return (1); + } + } + + if (!(options & PT_SILENT) && strcmp(dstinst, srcinst)) + (void) fprintf(stderr, pkg_gt(MSG_RENAME), dstinst); + + (void) sprintf(srcdir, "%s/%s", src, srcinst); + if (chdir(srcdir)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), srcdir); + return (1); + } + + if (ids_name) { /* unpack the datatstream into a directory */ + /* + * transfer pkginfo & pkgmap first + */ + (void) sprintf(cmd, "%s -pudm %s", CPIOPROC, dstdir); + if ((pp = epopen(cmd, "w")) == NULL) { + rpterr(); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_POPEN), cmd, errno); + return (1); + } + (void) fprintf(pp, "%s\n%s\n", PKGINFO, PKGMAP); + + sighold(SIGINT); + sighold(SIGHUP); + r = epclose(pp); + sigrelse(SIGINT); + sigrelse(SIGHUP); + + if (r != 0) { + rpterr(); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_PCLOSE), cmd, errno); + return (1); + } + + if (options & PT_INFO_ONLY) + return (0); /* don't transfer objects */ + + if (chdir(dstdir)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), dstdir); + return (1); + } + + /* + * for each part of the package, use cpio() to + * unpack the archive into the destination directory + */ + nparts = ds_findpkg(srcdev.cdevice, srcinst); + if (nparts < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + return (1); + } + for (part = 1; part <= nparts; /* void */) { + if (ds_getpkg(srcdev.cdevice, part, dstdir)) { + progerr(pkg_gt(ERR_TRANSFER)); + return (1); + } + part++; + if (dstdev.mount) { + (void) chdir("/"); + if (pkgumount(&dstdev)) + return (1); + if (part <= nparts) { + if (n = pkgmount(&dstdev, NULL, part+1, + nparts, 1)) + return (n); + if (ckoverwrite(dst, dstinst, options)) + return (1); + if (isdir(dstdir) && + mkdir(dstdir, 0755)) { + progerr( + pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MKDIR), + dstdir); + return (1); + } + /* + * since volume is removable, each part + * must contain a duplicate of the + * pkginfo file to properly identify the + * volume + */ + if (chdir(srcdir)) { + progerr( + pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), + srcdir); + return (1); + } + if ((pp = epopen(cmd, "w")) == NULL) { + rpterr(); + progerr( + pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_POPEN), + cmd, errno); + return (1); + } + (void) fprintf(pp, "pkginfo"); + + sighold(SIGINT); + sighold(SIGHUP); + r = epclose(pp); + sigrelse(SIGINT); + sigrelse(SIGHUP); + + if (r != 0) { + rpterr(); + progerr( + pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_PCLOSE), + cmd, errno); + return (1); + } + if (chdir(dstdir)) { + progerr( + pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), + dstdir); + return (1); + } + } + } + } + return (0); + } + + if ((fp = fopen(PKGMAP, "r")) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOPKGMAP), srcinst); + return (1); + } + + nparts = 1; + if (!rd_map_size(fp, &nparts, &maxpartsize, &compressedsize)) + return (1); + else + (void) fclose(fp); + + if (srcdev.mount) { + if (ckvolseq(srcdir, 1, nparts)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SEQUENCE)); + return (1); + } + } + + /* write each part of this package */ + if (options & PT_ODTSTREAM) { + char line[128]; + (void) mgets(line, 128); + curpartcnt = -1; + if (sscanf(line, "%s %d %d %[ 0-9]", &pkgname, &nparts, + &maxpartsize, volnos) == 4) { + sscanf(volnos, "%d %[ 0-9]", &curpartcnt, tmpvol); + strcpy(volnos, tmpvol); + } + } + + for (part = 1; part <= nparts; /* void */) { + if (curpartcnt == 0 && (options & PT_ODTSTREAM)) { + char prompt[128]; + int index; + ds_volno++; + (void) ds_close(0); + (void) sprintf(prompt, + pkg_gt("Insert %%v %d of %d into %%p"), + ds_volno, ds_volcnt); + if (n = getvol(ods_name, NULL, DM_FORMAT, prompt)) + return (n); + if ((ds_fd = open(dstdev.cdevice, O_WRONLY)) < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), dstdev.cdevice, + errno); + return (1); + } + if (ds_ginit(dstdev.cdevice) < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), dstdev.cdevice, + errno); + (void) ds_close(0); + return (1); + } + + (void) sscanf(volnos, "%d %[ 0-9]", &index, tmpvol); + (void) strcpy(volnos, tmpvol); + curpartcnt += index; + } + + if (options & PT_INFO_ONLY) + nparts = 0; + + if (part == 1) { + (void) sprintf(cmd, "find %s %s", PKGINFO, PKGMAP); + if (nparts && (isdir(INSTALL) == 0)) { + (void) strcat(cmd, " "); + (void) strcat(cmd, INSTALL); + } + } else + (void) sprintf(cmd, "find %s", PKGINFO); + + if (nparts > 1) { + (void) sprintf(temp, "%s.%d", RELOC, part); + if (iscpio(temp, &iscomp) || isdir(temp) == 0) { + (void) strcat(cmd, " "); + (void) strcat(cmd, temp); + } + (void) sprintf(temp, "%s.%d", ROOT, part); + if (iscpio(temp, &iscomp) || isdir(temp) == 0) { + (void) strcat(cmd, " "); + (void) strcat(cmd, temp); + } + (void) sprintf(temp, "%s.%d", ARCHIVE, part); + if (isdir(temp) == 0) { + (void) strcat(cmd, " "); + (void) strcat(cmd, temp); + } + } else if (nparts) { + for (i = 0; reloc_names[i] != NULL; i++) { + if (iscpio(reloc_names[i], &iscomp) || + isdir(reloc_names[i]) == 0) { + (void) strcat(cmd, " "); + (void) strcat(cmd, reloc_names[i]); + } + } + for (i = 0; root_names[i] != NULL; i++) { + if (iscpio(root_names[i], &iscomp) || + isdir(root_names[i]) == 0) { + (void) strcat(cmd, " "); + (void) strcat(cmd, root_names[i]); + } + } + if (isdir(ARCHIVE) == 0) { + (void) strcat(cmd, " "); + (void) strcat(cmd, ARCHIVE); + } + } + if (options & PT_ODTSTREAM) { +#ifndef SUNOS41 + (void) sprintf(cmd+strlen(cmd), + " -print | %s -ocD -C %d", +#else + (void) sprintf(cmd+strlen(cmd), + " -print | %s -oc -C %d", +#endif + CPIOPROC, (int)BLK_SIZE); + } else { + if (statvfs64(dstdir, &svfsb) == -1) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_STATVFS), dstdir, errno); + return (1); + } + + free_blocks = (((long)svfsb.f_frsize > 0) ? + howmany(svfsb.f_frsize, DEV_BSIZE) : + howmany(svfsb.f_bsize, DEV_BSIZE)) * svfsb.f_bavail; + + if ((has_comp_size ? compressedsize : maxpartsize) > + free_blocks) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOSPACE)); + return (1); + } + (void) sprintf(cmd+strlen(cmd), " -print | %s -pdum %s", + CPIOPROC, dstdir); + } + + n = esystem(cmd, -1, (options & PT_ODTSTREAM) ? ds_fd : -1); + if (n) { + rpterr(); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CMDFAIL), cmd, n); + return (1); + } + + part++; + if (srcdev.mount && (nparts > 1)) { + /* unmount current source volume */ + (void) chdir("/"); + if (pkgumount(&srcdev)) + return (1); + /* loop until volume is mounted successfully */ + while (part <= nparts) { + /* read only */ + n = pkgmount(&srcdev, NULL, part, nparts, 1); + if (n) + return (n); + if (chdir(srcdir)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CORRUPT), srcdir); + (void) chdir("/"); + pkgumount(&srcdev); + continue; + } + if (ckvolseq(srcdir, part, nparts)) { + (void) chdir("/"); + pkgumount(&srcdev); + continue; + } + break; + } + } + if (!(options & PT_ODTSTREAM) && dstdev.mount) { + /* unmount current volume */ + if (pkgumount(&dstdev)) + return (1); + /* loop until next volume is mounted successfully */ + while (part <= nparts) { + /* writable */ + n = pkgmount(&dstdev, NULL, part, nparts, 1); + if (n) + return (n); + if (ckoverwrite(dst, dstinst, options)) + continue; + if (isdir(dstdir) && mkdir(dstdir, 0755)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_MKDIR), dstdir); + continue; + } + break; + } + } + + if ((options & PT_ODTSTREAM) && part <= nparts) { + if (curpartcnt >= 0 && part > curpartcnt) { + char prompt[128]; + int index; + ds_volno++; + if (ds_close(0)) + return (1); + (void) sprintf(prompt, + pkg_gt("Insert %%v %d of %d into %%p"), + ds_volno, ds_volcnt); + if (n = getvol(ods_name, NULL, DM_FORMAT, + prompt)) + return (n); + if ((ds_fd = open(dstdev.cdevice, 1)) < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), + dstdev.cdevice, errno); + return (1); + } + if (ds_ginit(dstdev.cdevice) < 0) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_OPEN), + dstdev.cdevice, errno); + (void) ds_close(0); + return (1); + } + + (void) sscanf(volnos, "%d %[ 0-9]", &index, + tmpvol); + (void) strcpy(volnos, tmpvol); + curpartcnt += index; + } + } + + } + return (0); +} + +/* + * Name: pkgdump + * Description: Dump a cpio archive of a package's contents to a BIO. + * + * Arguments: srcinst - Name of package, which resides on the + * device pointed to by the static 'srcdev' variable, + * to dump. + * bio - BIO object to dump data to + * + * Returns : 0 - success + * nonzero - failure. errors printed to screen. + */ +static int +pkgdump(char *srcinst, BIO *bio) +{ + FILE *fp; + char *src; + char temp[MAXPATHLEN], + srcdir[MAXPATHLEN], + cmd[CMDSIZE]; + int i, n, part, nparts, maxpartsize, iscomp; + + /* + * when this routine is entered, the entire package + * is already available at 'src' - including the + * pkginfo/pkgmap files and the objects as well. + */ + + /* read the pkgmap to get it's size information */ + if ((fp = fopen(PKGMAP, "r")) == NULL) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_NOPKGMAP), srcinst); + return (1); + } + + nparts = 1; + if (!rd_map_size(fp, &nparts, &maxpartsize, &compressedsize)) + return (1); + else + (void) fclose(fp); + + /* make sure the first volume is available */ + if (srcdev.mount) { + src = srcdev.dirname; + (void) snprintf(srcdir, MAXPATHLEN, "%s/%s", src, srcinst); + if (ckvolseq(srcdir, 1, nparts)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_SEQUENCE)); + return (1); + } + } + + /* + * form cpio command that will output the contents of all of + * this package's parts + */ + for (part = 1; part <= nparts; /* void */) { + + if (part == 1) { + (void) snprintf(cmd, CMDSIZE, "find %s %s", + PKGINFO, PKGMAP); + if (nparts && (isdir(INSTALL) == 0)) { + (void) strcat(cmd, " "); + (void) strcat(cmd, INSTALL); + } + } else + (void) snprintf(cmd, CMDSIZE, "find %s", PKGINFO); + + if (nparts > 1) { + (void) snprintf(temp, MAXPATHLEN, "%s.%d", RELOC, part); + if (iscpio(temp, &iscomp) || isdir(temp) == 0) { + (void) strlcat(cmd, " ", CMDSIZE); + (void) strlcat(cmd, temp, CMDSIZE); + } + (void) snprintf(temp, MAXPATHLEN, "%s.%d", ROOT, part); + if (iscpio(temp, &iscomp) || isdir(temp) == 0) { + (void) strlcat(cmd, " ", CMDSIZE); + (void) strlcat(cmd, temp, CMDSIZE); + } + (void) snprintf(temp, MAXPATHLEN, "%s.%d", + ARCHIVE, part); + if (isdir(temp) == 0) { + (void) strlcat(cmd, " ", CMDSIZE); + (void) strlcat(cmd, temp, CMDSIZE); + } + } else if (nparts) { + for (i = 0; reloc_names[i] != NULL; i++) { + if (iscpio(reloc_names[i], &iscomp) || + isdir(reloc_names[i]) == 0) { + (void) strlcat(cmd, " ", CMDSIZE); + (void) strlcat(cmd, reloc_names[i], + CMDSIZE); + } + } + for (i = 0; root_names[i] != NULL; i++) { + if (iscpio(root_names[i], &iscomp) || + isdir(root_names[i]) == 0) { + (void) strlcat(cmd, " ", CMDSIZE); + (void) strlcat(cmd, root_names[i], + CMDSIZE); + } + } + if (isdir(ARCHIVE) == 0) { + (void) strlcat(cmd, " ", CMDSIZE); + (void) strlcat(cmd, ARCHIVE, CMDSIZE); + } + } + +#ifndef SUNOS41 + (void) sprintf(cmd+strlen(cmd), + " -print | %s -ocD -C %d", +#else + (void) sprintf(cmd+strlen(cmd), + " -print | %s -oc -C %d", +#endif + CPIOPROC, (int)BLK_SIZE); + /* + * execute the command, dumping all standard output + * to the BIO. + */ + n = BIO_dump_cmd(cmd, bio); + if (n != 0) { + rpterr(); + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CMDFAIL), cmd, n); + return (1); + } + + part++; + } + return (0); +} + +static void +sigtrap(int signo) +{ + signal_received++; +} + +static void +cleanup(void) +{ + chdir("/"); + if (tmpdir) { + rrmdir(tmpdir); + free(tmpdir); + tmpdir = NULL; + } + + if (tmppath) { + /* remove any previous tmppath stuff */ + rrmdir(tmppath); + free(tmppath); + tmppath = NULL; + } + + if (tmpsymdir) { + /* remove temp symbolic links made for signed pkg */ + rrmdir(tmpsymdir); + free(tmpsymdir); + tmpsymdir = NULL; + } + + if (srcdev.mount && !ids_name) + pkgumount(&srcdev); + if (dstdev.mount && !ods_name) + pkgumount(&dstdev); + (void) ds_close(1); +} + +/* + * Name: dump_hdr_and_pkgs + * Description: Dumps datastream header and each package's contents + * to the supplied BIO + * + * Arguments: bio - BIO object to dump data to + * hdr - Header for the datastream being dumped + * pkglist - NULL-terminated list of packages + * to dump. The location of the packages are stored + * in the static 'srcdev' variable. + * + * Returns : 0 - success + * nonzero - failure. errors printed to screen. + */ +static int +dump_hdr_and_pkgs(BIO *bio, struct dm_buf *hdr, char **pkglist) +{ + int block_cnt, i; + char srcdir[MAXPATHLEN]; + char cwd[MAXPATHLEN + 1]; + char *src; + + /* write out the header to the signature stream */ + for (block_cnt = 0; block_cnt < hdr->allocation; + block_cnt += BLK_SIZE) { + BIO_write(bio, (hdr->text_buffer + block_cnt), BLK_SIZE); + } + + /* save current directory */ + if (getcwd(cwd, MAXPATHLEN + 1) == NULL) { + logerr(pkg_gt(ERR_GETWD)); + progerr(pkg_gt(ERR_TRANSFER)); + return (1); + } + + /* now write out each package's contents */ + for (i = 0; pkglist[i]; i++) { + /* + * change to the source dir, so we can find and dump + * the package(s) bits into the BIO + * + */ + src = srcdev.dirname; + + /* change to the package source directory */ + (void) snprintf(srcdir, MAXPATHLEN, "%s/%s", src, pkglist[i]); + if (chdir(srcdir)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), srcdir); + return (1); + } + + if (pkgdump(pkglist[i], bio)) { + pkglist[i] = NULL; + return (1); + } + } + + /* change back to directory we were in upon entering this routine */ + if (chdir(cwd)) { + progerr(pkg_gt(ERR_TRANSFER)); + logerr(pkg_gt(MSG_CHDIR), cwd); + return (1); + } + + return (0); +} + +/* + * Name: BIO_dump_cmd + * Description: Dump the output of invoking a command + * to a BIO. + * + * Arguments: cmd - Command to invoke + * bio - BIO to dump output of command to + * only 'stdout' is dumped. + * Returns : 0 - success + * nonzero - failure. errors printed to screen. + */ +int +BIO_dump_cmd(char *cmd, BIO *bio) +{ + char buf[BLK_SIZE]; + FILE *fp; + int rc; + + /* start up the process */ + if ((fp = epopen(cmd, "r")) == NULL) { + rpterr(); + return (1); + } + + /* read output in chunks, transfer to BIO */ + while (fread(buf, BLK_SIZE, 1, fp) == 1) { + if (BIO_write(bio, buf, BLK_SIZE) != BLK_SIZE) { + sighold(SIGINT); + sighold(SIGHUP); + (void) epclose(fp); + sigrelse(SIGINT); + sigrelse(SIGHUP); + rpterr(); + return (1); + } + } + + /* done with stream, make sure no errors were encountered */ + if (ferror(fp)) { + (void) epclose(fp); + rpterr(); + return (1); + } + + /* done, close stream, report any errors */ + sighold(SIGINT); + sighold(SIGHUP); + rc = epclose(fp); + sigrelse(SIGINT); + sigrelse(SIGHUP); + if (rc != 0) { + rpterr(); + return (1); + } + + return (rc); +} diff --git a/usr/src/lib/libpkg/common/pkgweb.c b/usr/src/lib/libpkg/common/pkgweb.c new file mode 100644 index 0000000000..56559a0953 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgweb.c @@ -0,0 +1,3238 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <pkglocs.h> +#include <locale.h> +#include <libintl.h> +#include <libgen.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/types.h> +#include <fcntl.h> +#include <dirent.h> +#include <boot_http.h> +#include <errno.h> +#include <ctype.h> +#include <openssl/pkcs7.h> +#include <openssl/ocsp.h> +#include <openssl/pkcs12.h> +#include <openssl/err.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/rand.h> +#include <openssl/x509v3.h> +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" +#include "keystore.h" +#include "pkgweb.h" +#include "pkgerr.h" +#include "p12lib.h" + +/* fixed format when making an OCSP request */ +#define OCSP_REQUEST_FORMAT \ + "POST %s HTTP/1.0\r\n" \ + "Content-Type: application/ocsp-request\r\n" \ + "Content-Length: %d\r\n\r\n" + +/* + * no security is afforded by using this phrase to "encrypt" CA certificates, + * but it might aid in debugging and has to be non-null + */ +#define WEB_CA_PHRASE "schizophrenic" + +/* This one needs the ': ' at the end */ +#define CONTENT_TYPE_HDR "Content-Type" +#define CONTENT_DISPOSITION_HDR "Content-Disposition" +#define CONTENT_OCSP_RESP "application/ocsp-response" +#define CONTENT_LENGTH_HDR "Content-Length" +#define LAST_MODIFIED_HDR "Last-Modified" +#define OCSP_BUFSIZ 1024 + +/* + * default amount of time that is allowed for error when checking + * OCSP response validity. + * For example, if this is set to 5 minutes, then if a response + * is issued that is valid from 12:00 to 1:00, then we will + * accept it if the local time is between 11:55 and 1:05. + * This takes care of not-quite-synchronized server and client clocks. + */ +#define OCSP_VALIDITY_PERIOD (5 * 60) + +/* this value is defined by getpassphrase(3c) manpage */ +#define MAX_PHRASELEN 257 + +/* Max length of "enter password again" prompt message */ +#define MAX_VERIFY_MSGLEN 1024 + +/* local prototypes */ +static boolean_t remove_dwnld_file(char *); +static boolean_t get_ENV_proxyport(PKG_ERR *, ushort_t *); +static boolean_t make_link(char *, char *); +static WebStatus web_send_request(PKG_ERR *, int, int, int); +static boolean_t web_eval_headers(PKG_ERR *); +static WebStatus web_get_file(PKG_ERR *, char *, int, char **); +static boolean_t ck_dwnld_dir_space(PKG_ERR *, char *, ulong_t); +static WebStatus web_connect(PKG_ERR *); +static boolean_t web_setup(PKG_ERR *); +static boolean_t check_dwnld_dir(PKG_ERR *, char *); +static boolean_t parse_url_proxy(PKG_ERR *, char *, char *, ushort_t); +static boolean_t web_disconnect(void); +static char *get_unique_filename(char *, char *); +static boolean_t get_ENV_proxy(PKG_ERR *, char **); +static char *condense_lastmodified(char *); +static int web_verify(int, X509_STORE_CTX *); +static int get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); +static boolean_t get_ocsp_uri(X509 *, char **); +static OCSPStatus ocsp_verify(PKG_ERR *, X509 *, X509 *, char *, url_hport_t *, + STACK_OF(X509) *); +static char *get_time_string(ASN1_GENERALIZEDTIME *); +static char *write_ca_file(PKG_ERR *, char *, STACK_OF(X509) *, char *); +static boolean_t _get_random_info(void *, int); +static boolean_t init_session(void); +static void progress_setup(int, ulong_t); +static void progress_report(int, ulong_t); +static void progress_finish(int); +static char *replace_token(char *, char, char); +static void dequote(char *); +static void trim(char *); + + +/* + * structure used to hold data passed back to the + * X509 verify callback routine in validate_signature() + */ +typedef struct { + url_hport_t *proxy; + PKG_ERR *err; + STACK_OF(X509) *cas; +} verify_cb_data_t; + +/* Progress bar variables */ +static ulong_t const_increment, const_divider, completed, const_completed; + +/* current network backoff wait period */ +static int cur_backoff = 0; + +/* download session context handle */ +static WEB_SESSION *ps; + +static int webpkg_install = 0; +static char *prompt = NULL; +static char *passarg = NULL; + + +/* ~~~~~~~~~~~~~~ Public Functions ~~~~~~~~~~~~~~~~~~~ */ + +/* + * Name: set_prompt + * Description: Specifies the prompt to use with the pkglib + * passphrase callback routine. + * + * Arguments: newprompt - The prompt to display + * + * Returns : NONE + */ +void +set_passphrase_prompt(char *newprompt) +{ + prompt = newprompt; +} + +/* + * Name: set_passarg + * Description: Specifies the passphrase retrieval method + * to use with the pkglib + * passphrase callback routine. + * + * Arguments: newpassarg - The new password retrieval arg + * + * Returns : NONE + */ +void +set_passphrase_passarg(char *newpassarg) +{ + passarg = newpassarg; +} + +/* + * Name: get_proxy_port + * Description: Resolves proxy specification + * + * Arguments: err - where to record any errors. + * proxy - Location to store result - if *proxy is not + * null, then it will be validated, but not changed + * + * Returns : B_TRUE - success, B_FALSE otherwise + * on success, *proxy and *port are set to either + * the user-supplied proxy and port, or the + * ones found in the environment variables + * HTTPPROXY and/or HTTPROXYPORT + */ +boolean_t +get_proxy_port(PKG_ERR *err, char **proxy, ushort_t *port) +{ + if (*proxy != NULL) { + if (!path_valid(*proxy)) { + /* bad proxy supplied */ + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_BAD_PROXY), *proxy); + return (B_FALSE); + } + if (!get_ENV_proxyport(err, port)) { + /* env set, but bad */ + return (B_FALSE); + } + } else { + if (!get_ENV_proxy(err, proxy)) { + /* environment variable set, but bad */ + return (B_FALSE); + } + if ((*proxy != NULL) && !path_valid(*proxy)) { + /* env variable set, but bad */ + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_BAD_PROXY), *proxy); + return (B_FALSE); + } + if (!get_ENV_proxyport(err, port)) { + /* env variable set, but bad */ + return (B_FALSE); + } + } + return (B_TRUE); +} + +/* + * Name: path_valid + * Description: Checks a string for being a valid path + * + * Arguments: path - path to validate + * + * Returns : B_TRUE - success, B_FALSE otherwise. + * B_FALSE means path was null, too long (>PATH_MAX), + * or too short (<1) + */ +boolean_t +path_valid(char *path) +{ + if (path == NULL) { + return (B_FALSE); + } else if (strlen(path) > PATH_MAX) { + return (B_FALSE); + } else if (strlen(path) >= 1) { + return (B_TRUE); + } else { + /* path < 1 */ + return (B_FALSE); + } +} + +/* + * Name: web_cleanup + * Description: Deletes temp files, closes, frees memory taken + * by 'ps' static structure + * + * Arguments: none + * + * Returns : none + */ +void +web_cleanup(void) +{ + PKG_ERR *err; + + if (ps == NULL) + return; + + err = pkgerr_new(); + + if (ps->keystore) { + (void) close_keystore(err, ps->keystore, NULL); + } + + ps->keystore = NULL; + + pkgerr_free(err); + + if (ps->uniqfile) { + (void) remove_dwnld_file(ps->uniqfile); + free(ps->uniqfile); + ps->uniqfile = NULL; + } + if (ps->link) { + (void) remove_dwnld_file(ps->link); + free(ps->link); + ps->link = NULL; + } + if (ps->dwnld_dir) { + (void) rmdir(ps->dwnld_dir); + ps->dwnld_dir = NULL; + } + if (ps->errstr) { + free(ps->errstr); + ps->errstr = NULL; + } + + if (ps->content) { + free(ps->content); + ps->content = NULL; + } + + if (ps->resp) { + http_free_respinfo(ps->resp); + ps->resp = NULL; + } + + if (ps) { + free(ps); + ps = NULL; + } +} + +/* + * Name: web_session_control + * Description: Downloads an arbitrary URL and saves to disk. + * + * Arguments: err - where to record any errors. + * url - URL pointing to content to download - can be + * http:// or https:// + * dwnld_dir - Directory to download into + * keystore - keystore to use for accessing trusted + * certs when downloading using SSL + * proxy - HTTP proxy to use, or NULL for no proxy + * proxy_port - HTTP proxy port to use, ignored + * if proxy is NULL + * passarg - method to retrieve password + * retries - # of times to retry download before + * giving up + * timeout - how long to wait before retrying, + * when download is interrupted + * nointeract - if non-zero, do not output + * download progress to screen + * + * Returns : B_TRUE - success, B_FALSE otherwise + */ +boolean_t +web_session_control(PKG_ERR *err, char *url, char *dwnld_dir, + keystore_handle_t keystore, char *proxy, ushort_t proxy_port, + int retries, int timeout, int nointeract, char **fname) +{ + int i; + boolean_t ret = B_TRUE; + boolean_t retrieved = B_FALSE; + + if (!init_session()) { + ret = B_FALSE; + goto cleanup; + } + + if (!parse_url_proxy(err, url, proxy, proxy_port)) { + ret = B_FALSE; + goto cleanup; + } + + ps->timeout = timeout; + + if (keystore != NULL) + ps->keystore = keystore; + + if (dwnld_dir != NULL) + ps->dwnld_dir = xstrdup(dwnld_dir); + else { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_NO_DWNLD_DIR)); + ret = B_FALSE; + goto cleanup; + } + + if (!check_dwnld_dir(err, dwnld_dir)) { + ret = B_FALSE; + goto cleanup; + } + + for (i = 0; i < retries && !retrieved; i++) { + if (!web_setup(err)) { + ret = B_FALSE; + goto cleanup; + } + + switch (web_connect(err)) { + /* time out and wait a little bit for these failures */ + case WEB_OK: + /* were able to connect */ + reset_backoff(); + break; + case WEB_TIMEOUT: + echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); + (void) web_disconnect(); + backoff(); + continue; + + case WEB_CONNREFUSED: + echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), + ps->url.hport.hostname); + (void) web_disconnect(); + backoff(); + continue; + case WEB_HOSTDOWN: + echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), + ps->url.hport.hostname); + (void) web_disconnect(); + backoff(); + continue; + + default: + /* every other failure is a hard failure, so bail */ + ret = B_FALSE; + goto cleanup; + } + + switch (web_send_request(err, HTTP_REQ_TYPE_HEAD, + ps->data.cur_pos, ps->data.content_length)) { + case WEB_OK: + /* were able to connect */ + reset_backoff(); + break; + case WEB_TIMEOUT: + echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); + (void) web_disconnect(); + backoff(); + continue; + + case WEB_CONNREFUSED: + echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), + ps->url.hport.hostname); + (void) web_disconnect(); + backoff(); + continue; + case WEB_HOSTDOWN: + echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), + ps->url.hport.hostname); + (void) web_disconnect(); + backoff(); + continue; + default: + /* every other case is failure, so bail */ + ret = B_FALSE; + goto cleanup; + } + + if (!web_eval_headers(err)) { + ret = B_FALSE; + goto cleanup; + } + + switch (web_get_file(err, dwnld_dir, nointeract, fname)) { + case WEB_OK: + /* were able to retrieve file */ + retrieved = B_TRUE; + reset_backoff(); + break; + + case WEB_TIMEOUT: + echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); + (void) web_disconnect(); + backoff(); + continue; + + case WEB_CONNREFUSED: + echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), + ps->url.hport.hostname); + (void) web_disconnect(); + backoff(); + continue; + case WEB_HOSTDOWN: + echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), + ps->url.hport.hostname); + (void) web_disconnect(); + backoff(); + continue; + default: + /* every other failure is a hard failure, so bail */ + ret = B_FALSE; + goto cleanup; + } + } + + if (!retrieved) { + /* max retries attempted */ + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_DWNLD_FAILED), retries); + ret = B_FALSE; + } +cleanup: + (void) web_disconnect(); + if (!ret) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_DWNLD), url); + } + return (ret); +} + +/* + * Name: get_signature + * Description: retrieves signature from signed package. + * + * Arguments: err - where to record any errors. + * ids_name - name of package stream, for error reporting + * devp - Device on which package resides that we + * result - where to store resulting PKCS7 signature + * + * Returns : B_TRUE - package is signed and signature returned OR + * package is not signed, in which case result is NULL + * + * B_FALSE - there were problems accessing signature, + * and it is unknown whether it is signed or not. Errors + * recorded in 'err'. + */ +boolean_t +get_signature(PKG_ERR *err, char *ids_name, struct pkgdev *devp, PKCS7 **result) +{ + char path[PATH_MAX]; + int len, fd = -1; + struct stat buf; + FILE *fp = NULL; + boolean_t ret = B_TRUE; + BIO *sig_in = NULL; + PKCS7 *p7 = NULL; + + /* + * look for signature. If one was in the stream, + * it is now extracted + */ + if (((len = snprintf(path, PATH_MAX, "%s/%s", devp->dirname, + SIGNATURE_FILENAME)) >= PATH_MAX) || (len < 0)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), ids_name); + ret = B_FALSE; + goto cleanup; + } + + if ((fd = open(path, O_RDONLY|O_NONBLOCK)) == -1) { + /* + * only if the signature is non-existant + * do we "pass" + */ + if (errno != ENOENT) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPENSIG), + strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + } else { + /* found sig file. parse it. */ + if (fstat(fd, &buf) == -1) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_OPENSIG), strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + + if (!S_ISREG(buf.st_mode)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPENSIG), + (gettext(ERR_NOT_REG))); + ret = B_FALSE; + goto cleanup; + } + + if ((fp = fdopen(fd, "r")) == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_OPENSIG), strerror(errno)); + ret = B_FALSE; + goto cleanup; + } + + /* + * read in signature. If it's invalid, we + * punt, unless we're ignoring it + */ + if ((sig_in = BIO_new_fp(fp, BIO_NOCLOSE)) == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_OPENSIG), strerror(errno)); + goto cleanup; + } + + if ((p7 = PEM_read_bio_PKCS7(sig_in, + NULL, NULL, NULL)) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), + ids_name); + ret = B_FALSE; + goto cleanup; + } + *result = p7; + p7 = NULL; + } + +cleanup: + if (sig_in) + (void) BIO_free(sig_in); + if (fp) + (void) fclose(fp); + if (fd != -1) + (void) close(fd); + if (p7) + (void) PKCS7_free(p7); + + return (ret); +} + +/* + * Name: echo_out + * Description: Conditionally output a message to stdout + * + * Arguments: nointeract - if non-zero, do not output anything + * fmt - print format + * ... - print arguments + * + * Returns : none + */ +void +echo_out(int nointeract, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if (nointeract) + return; + + (void) vfprintf(stdout, fmt, ap); + + va_end(ap); + + (void) putc('\n', stdout); +} + +/* + * Name: strip_port + * Description: Returns "port" portion of a "hostname:port" string + * + * Arguments: proxy - full "hostname:port" string pointer + * + * Returns : the "port" portion of a "hostname:port" string, + * converted to a decimal integer, or (int)0 + * if string contains no :port suffix. + */ +ushort_t +strip_port(char *proxy) +{ + char *tmp_port; + + if ((tmp_port = strpbrk(proxy, ":")) != NULL) + return (atoi(tmp_port)); + else + return (0); +} + +/* + * Name: set_web_install + * Description: Sets flag indicating we are doing a web-based install + * + * Arguments: none + * + * Returns : none + */ +void +set_web_install(void) +{ + webpkg_install++; +} + +/* + * Name: is_web_install + * Description: Determines whether we are doing a web-based install + * + * Arguments: none + * + * Returns : non-zero if we are doing a web-based install, 0 otherwise + */ +int +is_web_install(void) +{ + return (webpkg_install); +} + +/* ~~~~~~~~~~~~~~ Private Functions ~~~~~~~~~~~~~~~~~~~ */ + +/* + * Name: web_disconnect + * Description: Disconnects connection to web server + * + * Arguments: none + * + * Returns : B_TRUE - successful disconnect, B_FALSE otherwise + * Temp certificiate files are deleted, + * if one was used to initiate the connection + * (such as when using SSL) + */ +static boolean_t +web_disconnect(void) +{ + if (ps->certfile) { + (void) unlink(ps->certfile); + } + if (http_srv_disconnect(ps->hps) == 0) + if (http_srv_close(ps->hps) == 0) + return (B_TRUE); + + return (B_FALSE); +} + +/* + * Name: check_dwnld_dir + * Description: Creates temp download directory + * + * Arguments: err - where to record any errors. + * dwnld_dir - name of directory to create + * + * Returns : B_TRUE - success, B_FALSE otherwise + * on success, directory is created with + * safe permissions + */ +static boolean_t +check_dwnld_dir(PKG_ERR *err, char *dwnld_dir) +{ + DIR *dirp; + + /* + * Check the directory passed in. If it doesn't exist, create it + * with strict permissions + */ + if ((dirp = opendir(dwnld_dir)) == NULL) { + if (mkdir(dwnld_dir, 0744) == -1) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), + dwnld_dir); + return (B_FALSE); + } + } + if (dirp) { + (void) closedir(dirp); + } + return (B_TRUE); +} + +/* + * Name: ds_validate_signature + * Description: Validates signature found in a package datastream + * + * Arguments: err - where to record any errors. + * pkgdev - Package context handle of package to verify + * pkgs - Null-terminated List of package name to verify + * ids_name - Pathname to stream to validate + * p7 - PKCS7 signature decoded from stream header + * cas - List of trusted CA certificates + * proxy - Proxy to use when doing online validation (OCSP) + * nointeract - if non-zero, do not output to screen + * + * Returns : B_TRUE - success, B_FALSE otherwise + * success means signature was completely validated, + * and contents of stream checked against signature. + */ +boolean_t +ds_validate_signature(PKG_ERR *err, struct pkgdev *pkgdev, char **pkgs, + char *ids_name, PKCS7 *p7, STACK_OF(X509) *cas, + url_hport_t *proxy, int nointeract) +{ + BIO *p7_bio; + boolean_t ret = B_TRUE; + + /* make sure it's a Signed PKCS7 message */ + if (!PKCS7_type_is_signed(p7)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG_TYPE), + ids_name); + ret = B_FALSE; + goto cleanup; + } + + /* initialize PKCS7 object to be filled in */ + if (!PKCS7_get_detached(p7)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG_DT), + ids_name); + ret = B_FALSE; + goto cleanup; + } + + /* dump header and packages into BIO to calculate the message digest */ + if ((p7_bio = PKCS7_dataInit(p7, NULL)) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), + ids_name); + ret = B_FALSE; + goto cleanup; + } + + if ((BIO_ds_dump_header(err, p7_bio) != 0) || + (BIO_ds_dump(err, ids_name, p7_bio) != 0)) { + ret = B_FALSE; + goto cleanup; + } + (void) BIO_flush(p7_bio); + + /* validate the stream and its signature */ + if (!validate_signature(err, ids_name, p7_bio, p7, cas, + proxy, nointeract)) { + ret = B_FALSE; + goto cleanup; + } + + /* reset device stream (really bad performance for tapes) */ + (void) ds_close(1); + (void) ds_init(ids_name, pkgs, pkgdev->norewind); + +cleanup: + return (ret); +} + + +/* + * Name: validate_signature + * Description: Validates signature of an arbitrary stream of bits + * + * Arguments: err - where to record any errors. + * name - Descriptive name of object being validated, + * for good error reporting messages + * indata - BIO object to read stream bits from + * p7 - PKCS7 signature of stream + * cas - List of trusted CA certificates + * proxy - Proxy to use when doing online validation (OCSP) + * nointeract - if non-zero, do not output to screen + * + * Returns : B_TRUE - success, B_FALSE otherwise + * success means signature was completely validated, + * and contents of stream checked against signature. + */ +boolean_t +validate_signature(PKG_ERR *err, char *name, BIO *indata, PKCS7 *p7, + STACK_OF(X509) *cas, url_hport_t *proxy, int nointeract) +{ + STACK_OF(PKCS7_SIGNER_INFO) *sec_sinfos = NULL; + + PKCS7_SIGNER_INFO *signer = NULL; + X509_STORE *sec_truststore = NULL; + X509_STORE_CTX *ctx = NULL; + X509 *signer_cert = NULL, *issuer = NULL; + STACK_OF(X509) *chaincerts = NULL; + int i, k; + unsigned long errcode; + const char *err_data = NULL; + const char *err_reason = NULL; + char *err_string; + int err_flags; + verify_cb_data_t verify_data; + char *signer_sname; + char *signer_iname; + PKCS7_ISSUER_AND_SERIAL *ias; + boolean_t ret = B_TRUE; + + /* only support signed PKCS7 signatures */ + if (!PKCS7_type_is_signed(p7)) { + PKCS7err(PKCS7_F_PKCS7_DATAVERIFY, PKCS7_R_WRONG_PKCS7_TYPE); + ret = B_FALSE; + goto cleanup; + } + + /* initialize temporary internal trust store used for verification */ + sec_truststore = X509_STORE_new(); + + for (i = 0; i < sk_X509_num(cas); i++) { + if (X509_STORE_add_cert(sec_truststore, + sk_X509_value(cas, i)) == 0) { + pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MEM)); + ret = B_FALSE; + goto cleanup; + } + } + + /* get signers from the signature */ + if ((sec_sinfos = PKCS7_get_signer_info(p7)) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), name); + ret = B_FALSE; + goto cleanup; + } + + /* verify each signer found in the PKCS7 signature */ + for (k = 0; k < sk_PKCS7_SIGNER_INFO_num(sec_sinfos); k++) { + signer = sk_PKCS7_SIGNER_INFO_value(sec_sinfos, k); + signer_cert = PKCS7_cert_from_signer_info(p7, signer); + signer_sname = get_subject_display_name(signer_cert); + signer_iname = get_issuer_display_name(signer_cert); + + echo_out(nointeract, gettext(MSG_VERIFY), signer_sname); + + /* find the issuer of the current cert */ + chaincerts = p7->d.sign->cert; + ias = signer->issuer_and_serial; + issuer = X509_find_by_issuer_and_serial(chaincerts, + ias->issuer, ias->serial); + + /* were we not able to find the issuer cert */ + if (issuer == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_VERIFY_ISSUER), + signer_iname, signer_sname); + ret = B_FALSE; + goto cleanup; + } + + /* Lets verify */ + if ((ctx = X509_STORE_CTX_new()) == NULL) { + pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MEM)); + ret = B_FALSE; + goto cleanup; + } + (void) X509_STORE_CTX_init(ctx, sec_truststore, + issuer, chaincerts); + (void) X509_STORE_CTX_set_purpose(ctx, + X509_PURPOSE_ANY); + + /* callback will perform OCSP on certificates with OCSP data */ + X509_STORE_CTX_set_verify_cb(ctx, web_verify); + + /* pass needed data into callback through the app_data handle */ + verify_data.proxy = proxy; + verify_data.cas = cas; + verify_data.err = err; + (void) X509_STORE_CTX_set_app_data(ctx, &verify_data); + + /* first verify the certificate chain */ + i = X509_verify_cert(ctx); + if (i <= 0 && ctx->error != X509_V_ERR_CERT_HAS_EXPIRED) { + signer_sname = + get_subject_display_name(ctx->current_cert); + signer_iname = + get_issuer_display_name(ctx->current_cert); + /* if the verify context holds an error, print it */ + if (ctx->error != X509_V_OK) { + pkgerr_add(err, PKGERR_VERIFY, + gettext(ERR_VERIFY_SIG), signer_sname, + signer_iname, + (char *)X509_verify_cert_error_string(ctx->error)); + } else { + /* some other error. print them all. */ + while ((errcode = ERR_get_error_line_data(NULL, + NULL, &err_data, &err_flags)) != 0) { + err_reason = + ERR_reason_error_string(errcode); + if (err_reason == NULL) { + err_reason = + gettext(ERR_SIG_INT); + } + + if (!(err_flags & ERR_TXT_STRING)) { + err_data = + gettext(ERR_SIG_INT); + } + err_string = + xmalloc(strlen(err_reason) + + strlen(err_data) + 3); + (void) sprintf(err_string, "%s: %s", + err_reason, err_data); + pkgerr_add(err, PKGERR_VERIFY, + gettext(ERR_VERIFY_SIG), + signer_sname, signer_iname, + err_string); + free(err_string); + } + } + ret = B_FALSE; + goto cleanup; + } + + /* now verify the signature */ + i = PKCS7_signatureVerify(indata, p7, signer, issuer); + + if (i <= 0) { + /* print out any OpenSSL-specific errors */ + signer_sname = + get_subject_display_name(ctx->current_cert); + signer_iname = + get_subject_display_name(ctx->current_cert); + while ((errcode = ERR_get_error_line_data(NULL, + NULL, &err_data, &err_flags)) != 0) { + err_reason = + ERR_reason_error_string(errcode); + if (err_reason == NULL) { + err_reason = + gettext(ERR_SIG_INT); + } + + if (!(err_flags & ERR_TXT_STRING)) { + err_data = + gettext(ERR_SIG_INT); + } + pkgerr_add(err, PKGERR_VERIFY, + gettext(ERR_VERIFY_SIG), signer_sname, + signer_iname, err_reason); + pkgerr_add(err, PKGERR_VERIFY, + gettext(ERR_VERIFY_SIG), signer_sname, + signer_iname, err_data); + } + ret = B_FALSE; + goto cleanup; + } + + echo_out(nointeract, gettext(MSG_VERIFY_OK), signer_sname); + } + + /* signature(s) verified successfully */ +cleanup: + if (ctx) + X509_STORE_CTX_cleanup(ctx); + return (ret); +} + +/* + * Name: web_verify + * Description: Callback used by PKCS7_dataVerify when + * verifying a certificate chain. + * + * Arguments: err - where to record any errors. + * ctx - The context handle of the current verification operation + * + * Returns : B_TRUE - success, B_FALSE otherwise + * if it's '0' (not OK) we simply return it, since the + * verification operation has already determined that the + * cert is invalid. if 'ok' is non-zero, then we do our + * checks, and return 0 or 1 based on if the cert is + * invalid or valid. + */ +static int +web_verify(int ok, X509_STORE_CTX *ctx) +{ + X509 *curr_cert; + X509 *curr_issuer; + char *uri; + url_hport_t *proxy; + PKG_ERR *err = NULL; + STACK_OF(X509) *cas; + if (!ok) { + /* don't override a verify failure */ + return (ok); + } + + + /* get app data supplied through callback context */ + err = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->err; + proxy = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->proxy; + cas = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->cas; + + /* Check revocation status */ + curr_cert = X509_STORE_CTX_get_current_cert(ctx); + + /* this shouldn't happen */ + if (curr_cert == NULL) { + pkgerr_add(err, PKGERR_INTERNAL, gettext(ERR_PKG_INTERNAL), + __FILE__, __LINE__); + return (0); + } + + /* don't perform OCSP unless cert has required OCSP extensions */ + if (get_ocsp_uri(curr_cert, &uri)) { + if (get_issuer(&curr_issuer, ctx, curr_cert) <= 0) { + /* no issuer! */ + pkgerr_add(err, PKGERR_INTERNAL, + gettext(ERR_PKG_INTERNAL), + __FILE__, __LINE__); + return (0); + } + + /* + * ok we have the current cert + * and its issuer. Do the OCSP check + */ + + /* + * OCSP extensions are, by, RFC 2459, never critical + * extensions, therefore, we only fail if we were able + * to explicitly contact an OCSP responder, and that + * responder did not indicate the cert was valid. We + * also fail if user-supplied data could not be parsed + * or we run out of memory. We succeeed for "soft" + * failures, such as not being able to connect to the + * OCSP responder, or trying to use if the OCSP URI + * indicates SSL must be used (which we do not + * support) + */ + switch (ocsp_verify(err, curr_cert, curr_issuer, + uri, proxy, cas)) { + case OCSPMem: /* Ran out of memory */ + case OCSPInternal: /* Some internal error */ + case OCSPVerify: /* OCSP responder indicated fail */ + return (0); + } + /* all other cases are success, or soft failures */ + pkgerr_clear(err); + } + + return (ok); +} + +/* + * Name: get_time_string + * Description: Generates a human-readable string from an ASN1_GENERALIZED_TIME + * + * Arguments: intime - The time to convert + * + * Returns : A pointer to a static string representing the passed-in time. + */ +static char +*get_time_string(ASN1_GENERALIZEDTIME *intime) +{ + + static char time[ATTR_MAX]; + BIO *mem; + char *p; + + if (intime == NULL) { + return (NULL); + } + if ((mem = BIO_new(BIO_s_mem())) == NULL) { + return (NULL); + } + + if (ASN1_GENERALIZEDTIME_print(mem, intime) == 0) { + (void) BIO_free(mem); + return (NULL); + } + + if (BIO_gets(mem, time, ATTR_MAX) <= 0) { + (void) BIO_free(mem); + return (NULL); + } + + (void) BIO_free(mem); + + /* trim the end of the string */ + for (p = time + strlen(time) - 1; isspace(*p); p--) { + *p = '\0'; + } + + return (time); +} + +/* + * Name: get_ocsp_uri + * Description: Examines an X509 certificate and retrieves the embedded + * OCSP Responder URI if one exists. + * + * Arguments: cert - The cert to inspect + * uri - pointer where the newly-allocated URI is placed, if found + * + * Returns : Success if the URI was found. Appropriate status otherwise. + */ +static boolean_t +get_ocsp_uri(X509 *cert, char **uri) +{ + AUTHORITY_INFO_ACCESS *aia; + ACCESS_DESCRIPTION *ad; + int i; + + if (getenv("PKGWEB_TEST_OCSP")) { + *uri = xstrdup(getenv("PKGWEB_TEST_OCSP")); + return (B_TRUE); + } + + /* get the X509v3 extension holding the OCSP URI */ + if ((aia = X509_get_ext_d2i(cert, NID_info_access, + NULL, NULL)) != NULL) { + for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) { + ad = sk_ACCESS_DESCRIPTION_value(aia, i); + if (OBJ_obj2nid(ad->method) == NID_ad_OCSP) { + if (ad->location->type == GEN_URI) { + *uri = + xstrdup((char *)ASN1_STRING_data(ad->location->d.ia5)); + return (B_TRUE); + } + } + } + } + + /* no URI was found */ + return (B_FALSE); +} + +/* + * Name: ocsp_verify + * Description: Attempts to contact an OCSP Responder and ascertain the validity + * of an X509 certificate. + * + * Arguments: err - Error object to add error messages to + * cert - The cert to validate + * issuer - The certificate of the issuer of 'cert' + * uri - The OCSP Responder URI + * cas - The trusted CA certificates used to verify the + * signed OCSP response + * Returns : Success - The OCSP Responder reported a 'good' + * status for the cert otherwise, appropriate + * error is returned. + */ +static OCSPStatus +ocsp_verify(PKG_ERR *err, X509 *cert, X509 *issuer, + char *uri, url_hport_t *proxy, STACK_OF(X509) *cas) +{ + OCSP_CERTID *id; + OCSP_REQUEST *req; + OCSP_RESPONSE *resp; + OCSP_BASICRESP *bs; + BIO *cbio, *mem; + char ocspbuf[OCSP_BUFSIZ]; + char *host = NULL, *portstr = NULL, *path = "/", *p, *q, *r; + int port, status, reason; + int len, retval, respcode, use_ssl = 0; + ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; + char *subjname; + time_t currtime; + char currtimestr[ATTR_MAX]; + unsigned long errcode; + const char *err_reason; + + subjname = get_subject_display_name(cert); + + /* parse the URI into its constituent parts */ + if (OCSP_parse_url(uri, &host, &portstr, &path, &use_ssl) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_PARSE), uri); + return (OCSPParse); + } + + /* we don't currently support SSL-based OCSP Responders */ + if (use_ssl) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_UNSUP), uri); + return (OCSPUnsupported); + } + + /* default port if none specified */ + if (portstr == NULL) { + port = (int)URL_DFLT_SRVR_PORT; + } else { + port = (int)strtoul(portstr, &r, 10); + if (*r != '\0') { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_PARSE), uri); + return (OCSPParse); + } + } + + /* allocate new request structure */ + if ((req = OCSP_REQUEST_new()) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); + return (OCSPMem); + } + + /* convert cert and issuer fields into OCSP request data */ + if ((id = OCSP_cert_to_id(NULL, cert, issuer)) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_PKG_INTERNAL), + __FILE__, __LINE__); + return (OCSPInternal); + } + + /* fill out request structure with request data */ + if ((OCSP_request_add0_id(req, id)) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_PKG_INTERNAL), + __FILE__, __LINE__); + return (OCSPInternal); + } + + /* add nonce */ + OCSP_request_add1_nonce(req, NULL, -1); + + /* connect to host, or proxy */ + if (proxy != NULL) { + if ((cbio = BIO_new_connect(proxy->hostname)) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); + return (OCSPMem); + } + + /* + * BIO_set_conn_int_port takes an int *, so let's give it one + * rather than an ushort_t * + */ + port = proxy->port; + (void) BIO_set_conn_int_port(cbio, &port); + if (BIO_do_connect(cbio) <= 0) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_CONNECT), + proxy->hostname, port); + return (OCSPConnect); + } + } else { + if ((cbio = BIO_new_connect(host)) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); + return (OCSPMem); + } + + (void) BIO_set_conn_int_port(cbio, &port); + if (BIO_do_connect(cbio) <= 0) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_CONNECT), + host, port); + return (OCSPConnect); + } + } + + /* calculate length of binary request data */ + len = i2d_OCSP_REQUEST(req, NULL); + + /* send the request headers */ + if (proxy != NULL) { + retval = BIO_printf(cbio, OCSP_REQUEST_FORMAT, uri, len); + } else { + retval = BIO_printf(cbio, OCSP_REQUEST_FORMAT, path, len); + } + + if (retval <= 0) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_SEND), host); + return (OCSPRequest); + } + + /* send the request binary data */ + if (i2d_OCSP_REQUEST_bio(cbio, req) <= 0) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_SEND), host); + return (OCSPRequest); + } + + /* + * read the response into a memory BIO, so we can 'gets' + * (socket bio's don't support BIO_gets) + */ + if ((mem = BIO_new(BIO_s_mem())) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); + return (OCSPMem); + } + + while ((len = BIO_read(cbio, ocspbuf, OCSP_BUFSIZ))) { + if (len < 0) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_READ), host); + return (OCSPRequest); + } + if (BIO_write(mem, ocspbuf, len) != len) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); + return (OCSPMem); + } + } + + /* now get the first line of the response */ + if (BIO_gets(mem, ocspbuf, OCSP_BUFSIZ) <= 0) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_RESP_PARSE)); + return (OCSPRequest); + } + + /* parse the header response */ + /* it should look like "HTTP/x.x 200 OK" */ + + /* skip past the protocol info */ + for (p = ocspbuf; (*p != '\0') && !isspace(*p); p++) + continue; + + /* skip past whitespace betwen protocol and start of response code */ + while ((*p != '\0') && isspace(*p)) { + p++; + } + + if (*p == '\0') { + /* premature end */ + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_RESP_PARSE), ocspbuf); + return (OCSPRequest); + } + + /* find end of response code */ + for (q = p; (*q != NULL) && !isspace(*q); q++) + continue; + + /* mark end of response code */ + *q++ = '\0'; + + /* parse response code */ + respcode = strtoul(p, &r, 10); + if (*r != '\0') { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_RESP_PARSE), ocspbuf); + return (OCSPRequest); + } + + /* now find beginning of the response string */ + while ((*q != NULL) && isspace(*q)) { + q++; + } + + /* trim whitespace from end of message */ + for (r = (q + strlen(q) - 1); isspace(*r); r--) { + *r = '\0'; + } + + /* response must be OK */ + if (respcode != 200) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_RESP_NOTOK), 200, + respcode, q); + return (OCSPRequest); + } + + /* read headers, looking for content-type or a blank line */ + while (BIO_gets(mem, ocspbuf, OCSP_BUFSIZ) > 0) { + + /* if we get a content type, make sure it's the right type */ + if (ci_strneq(ocspbuf, CONTENT_TYPE_HDR, + strlen(CONTENT_TYPE_HDR))) { + + /* look for the delimiting : */ + p = strchr(ocspbuf + strlen(CONTENT_TYPE_HDR), ':'); + + if (p == NULL) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_RESP_PARSE), ocspbuf); + return (OCSPResponder); + } + + /* skip over ':' */ + p++; + + /* find beginning of the content type */ + while ((*p != NULL) && isspace(*p)) { + p++; + } + + if (!ci_strneq(p, CONTENT_OCSP_RESP, + strlen(CONTENT_OCSP_RESP))) { + /* response is not right type */ + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_RESP_TYPE), + p, CONTENT_OCSP_RESP); + return (OCSPResponder); + } + + /* continue with next header line */ + continue; + } + + /* scan looking for a character */ + for (p = ocspbuf; (*p != '\0') && isspace(*p); p++) { + continue; + } + /* + * if we got to the end of the line with + * no chars, then this is a blank line + */ + if (*p == '\0') { + break; + } + } + + + if (*p != '\0') { + /* last line was not blank */ + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_RESP_PARSE), ocspbuf); + return (OCSPResponder); + } + + /* now read in the binary response */ + if ((resp = d2i_OCSP_RESPONSE_bio(mem, NULL)) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_READ), host); + return (OCSPResponder); + } + + /* free temp BIOs */ + (void) BIO_free(mem); + (void) BIO_free_all(cbio); + cbio = NULL; + + /* make sure request was successful */ + if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_RESP_NOTOK), + OCSP_RESPONSE_STATUS_SUCCESSFUL, + OCSP_response_status(resp), + OCSP_response_status_str(OCSP_response_status(resp))); + return (OCSPResponder); + } + + /* parse binary response into internal structure */ + if ((bs = OCSP_response_get1_basic(resp)) == NULL) { + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_READ), host); + return (OCSPParse); + } + + /* + * From here to the end of the code, the return values + * should be hard failures + */ + + /* verify the response, warn if no nonce */ + if (OCSP_check_nonce(req, bs) <= 0) { + logerr(pkg_gt(WRN_OCSP_RESP_NONCE)); + } + + if (OCSP_basic_verify(bs, cas, NULL, OCSP_TRUSTOTHER) <= 0) { + while ((errcode = ERR_get_error()) != NULL) { + err_reason = ERR_reason_error_string(errcode); + if (err_reason == NULL) { + err_reason = + gettext(ERR_SIG_INT); + } + pkgerr_add(err, PKGERR_PARSE, (char *)err_reason); + } + pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_VERIFY_FAIL), + uri); + return (OCSPVerify); + } + + /* check the validity of our certificate */ + if (OCSP_resp_find_status(bs, id, &status, &reason, + &rev, &thisupd, &nextupd) == NULL) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_VERIFY_NO_STATUS), subjname); + return (OCSPVerify); + } + + if ((currtime = time(NULL)) == (time_t)-1) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_VERIFY_NOTIME)); + return (OCSPVerify); + } + + (void) strlcpy(currtimestr, ctime(&currtime), ATTR_MAX); + + /* trim end */ + for (r = currtimestr + strlen(currtimestr) - 1; + isspace(*r); r--) { + *r = '\0'; + } + + if (!OCSP_check_validity(thisupd, nextupd, + OCSP_VALIDITY_PERIOD, -1)) { + if (nextupd != NULL) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_VERIFY_VALIDITY), + get_time_string(thisupd), get_time_string(nextupd), + currtimestr); + } else { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_VERIFY_VALIDITY), + get_time_string(thisupd), + currtimestr); + } + return (OCSPVerify); + } + + if (status != V_OCSP_CERTSTATUS_GOOD) { + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_OCSP_VERIFY_STATUS), subjname, + OCSP_cert_status_str(status)); + return (OCSPVerify); + } + + /* everythign checks out */ + return (OCSPSuccess); +} + +/* + * Name: get_issuer + * Description: Attempts to find the issuing certificate for a given certificate + * This will look in both the list of trusted certificates found in + * the X509_STORE_CTX structure, as well as the list of untrusted + * chain certificates found in the X509_STORE_CTX structure. + * Arguments: + * issuer - The resulting issuer cert is placed here, if found + * ctx - The current verification context + * x - The certificate whose issuer we are looking for + * Returns : Success - The issuer cert was found and placed in *issuer. + * otherwise, appropriate error is returned. + */ +static int +get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x) +{ + int i, ok; + + /* + * first look in the list of trusted + * certs, using the context's method to do so + */ + if ((ok = ctx->get_issuer(issuer, ctx, x)) > 0) { + return (ok); + } + + if (ctx->untrusted != NULL) { + /* didn't find it in trusted certs, look through untrusted */ + for (i = 0; i < sk_X509_num(ctx->untrusted); i++) { + if (X509_check_issued(sk_X509_value(ctx->untrusted, i), + x) == X509_V_OK) { + *issuer = sk_X509_value(ctx->untrusted, i); + return (1); + } + } + } + *issuer = NULL; + return (0); +} + +/* + * Name: parse_url_proxy + * Description: Parses URL and optional proxy specification, populates static + * 'ps' structure + * + * Arguments: err - where to record any errors. + * url - URL to parse + * proxy - proxy to parse, or NULL for no proxy + * proxy_port - Default proxy port to use if no proxy + * port specified in 'proxy' + * + * Returns : B_TRUE - success, B_FALSE otherwise + * on success, 'ps->url' and 'ps->proxy' are populated + * with parsed data. + */ +static boolean_t +parse_url_proxy(PKG_ERR *err, char *url, char *proxy, ushort_t proxy_port) +{ + boolean_t ret = B_TRUE; + if (!path_valid(url)) { + ret = B_FALSE; + goto cleanup; + } + + if (url_parse(url, &ps->url) != URL_PARSE_SUCCESS) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_PARSE_URL), url); + ret = B_FALSE; + goto cleanup; + } + + if (proxy != NULL) { + if (url_parse_hostport(proxy, &ps->proxy, proxy_port) + != URL_PARSE_SUCCESS) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_BAD_PROXY), proxy); + ret = B_FALSE; + goto cleanup; + } + } + +cleanup: + return (ret); +} + +/* + * Name: web_setup + * Description: Initializes http library settings + * + * Arguments: err - where to record any errors. + * + * Returns : B_TRUE - success, B_FALSE otherwise + */ +static boolean_t +web_setup(PKG_ERR *err) +{ + boolean_t ret = B_TRUE; + static boolean_t keepalive = B_TRUE; + + if ((ps->hps = http_srv_init(&ps->url)) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); + ret = B_FALSE; + goto cleanup; + } + + if (getenv("WEBPKG_DEBUG") != NULL) { + http_set_verbose(B_TRUE); + } + + if (ps->proxy.hostname[0] != '\0' && + http_set_proxy(ps->hps, &ps->proxy) != 0) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); + ret = B_FALSE; + goto cleanup; + } + if (http_set_keepalive(ps->hps, keepalive) != 0) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); + ret = B_FALSE; + goto cleanup; + } + if (http_set_socket_read_timeout(ps->hps, ps->timeout) != 0) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); + ret = B_FALSE; + goto cleanup; + } + if (http_set_random_file(ps->hps, RANDOM) != 0) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); + ret = B_FALSE; + goto cleanup; + } + + (void) http_set_p12_format(B_TRUE); + +cleanup: + return (ret); +} + +/* + * Name: web_connect + * Description: Makes connection with URL stored in static 'ps' structure. + * + * Arguments: err - where to record any errors. + * + * Returns : WEB_OK - connection successful + * WEB_VERIFY_SETUP - Unable to complete necessary + * SSL setup + * WEB_CONNREFUSED - Connection was refused to web site + * WEB_HOSTDOWN - Host was not responding to request + * WEB_NOCONNECT - Some other connection failure + */ +static WebStatus +web_connect(PKG_ERR *err) +{ + STACK_OF(X509) *sec_cas = NULL; + char *path; + WebStatus ret = WEB_OK; + ulong_t errcode; + uint_t errsrc; + int my_errno = 0; + const char *libhttperr = NULL; + + if (ps->url.https == B_TRUE) { + /* get CA certificates */ + if (find_ca_certs(err, ps->keystore, &sec_cas) != 0) { + ret = WEB_VERIFY_SETUP; + goto cleanup; + } + + if (sk_X509_num(sec_cas) < 1) { + /* no trusted websites */ + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_KEYSTORE_NOTRUST)); + ret = WEB_VERIFY_SETUP; + goto cleanup; + } + + /* + * write out all CA certs to temp file. libwanboot should + * have an interface for giving it a list of trusted certs + * through an in-memory structure, but currently that does + * not exist + */ + if ((path = write_ca_file(err, ps->dwnld_dir, sec_cas, + WEB_CA_PHRASE)) == NULL) { + ret = WEB_VERIFY_SETUP; + goto cleanup; + } + + ps->certfile = path; + if (http_set_password(ps->hps, WEB_CA_PHRASE) != 0) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTPS_PASSWD)); + ret = WEB_VERIFY_SETUP; + goto cleanup; + } + + if (http_set_certificate_authority_file(path) != 0) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTPS_CA)); + ret = WEB_VERIFY_SETUP; + goto cleanup; + } + } + + if (http_srv_connect(ps->hps) != 0) { + while ((errcode = http_get_lasterr(ps->hps, &errsrc)) != 0) { + /* Have an error - is it EINTR? */ + if (errsrc == ERRSRC_SYSTEM) { + my_errno = errcode; + break; + } else if (libhttperr == NULL) { + /* save the first non-system error message */ + libhttperr = http_errorstr(errsrc, errcode); + } + } + switch (my_errno) { + case EINTR: + case ETIMEDOUT: + /* Timed out. Try, try again */ + ret = WEB_TIMEOUT; + break; + case ECONNREFUSED: + ret = WEB_CONNREFUSED; + break; + case EHOSTDOWN: + ret = WEB_HOSTDOWN; + break; + default: + /* some other fatal error */ + ret = WEB_NOCONNECT; + if (libhttperr == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_INIT_CONN), + ps->url.hport.hostname); + } else { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTP), libhttperr); + } + break; + } + } +cleanup: + return (ret); +} + +/* + * Name: write_ca_file + * Description: Writes out a PKCS12 file containing all trusted certs + * found in keystore recorded in static 'ps' structure + * + * This routine is used because the libwanboot library's + * HTTPS routines cannot accept trusted certificates + * through an in-memory structure, when initiating an + * SSL connection. They must be in a PKCS12, which is + * admittedly a poor interface. + * + * Arguments: err - where to record any errors. + * tmpdir - Directory to write certificate file in + * cacerts - Certs to write out + * passwd - password used to encrypt certs + * + * Returns : path to resulting file, if successfullly written, + * otherwise NULL. + */ +static char +*write_ca_file(PKG_ERR *err, char *tmpdir, STACK_OF(X509) *cacerts, + char *passwd) +{ + int fd, len; + FILE *fp; + PKCS12 *p12 = NULL; + char *ret = NULL; + static char tmp_file[PATH_MAX] = ""; + struct stat buf; + + if (!path_valid(tmpdir)) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), tmpdir); + goto cleanup; + } + + /* mkstemp replaces XXXXXX with a unique string */ + if (((len = snprintf(tmp_file, PATH_MAX, "%s/%sXXXXXX", tmpdir, + "cert")) < 0) || + (len >= PATH_MAX)) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), tmpdir); + goto cleanup; + } + + if ((fd = mkstemp(tmp_file)) == -1) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); + goto cleanup; + } + + if (fstat(fd, &buf) == -1) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); + goto cleanup; + } + + if (!S_ISREG(buf.st_mode)) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); + goto cleanup; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); + goto cleanup; + } + + if ((p12 = sunw_PKCS12_create(passwd, NULL, NULL, cacerts)) == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_KEYSTORE_FORM), tmp_file); + goto cleanup; + } + + if (i2d_PKCS12_fp(fp, p12) == 0) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_KEYSTORE_FORM), tmp_file); + goto cleanup; + } + + (void) fflush(fp); + (void) fclose(fp); + (void) close(fd); + fp = NULL; + fd = -1; + ret = tmp_file; + +cleanup: + if (p12 != NULL) + PKCS12_free(p12); + if (fp != NULL) + (void) fclose(fp); + if (fd != -1) { + (void) close(fd); + (void) unlink(tmp_file); + } + + return (ret); +} + +/* + * Name: web_send_request + * Description: Sends an HTTP request for a file to the + * web server being communicated with in the static + * 'ps' structure + * + * Arguments: err - where to record any errors. + * request_type - HTTP_REQ_TYPE_HEAD to send an HTTP HEAD request, + * or HTTP_REQ_TYPE_GET to send an HTTP GET request + * cp - + * Returns : WEB_OK - request sent successfully + * WEB_CONNREFUSED - Connection was refused to web site + * WEB_HOSTDOWN - Host was not responding to request + * WEB_NOCONNECT - Some other connection failure + */ +static WebStatus +web_send_request(PKG_ERR *err, int request_type, int cp, int ep) +{ + WebStatus ret = WEB_OK; + ulong_t errcode; + uint_t errsrc; + int my_errno = 0; + const char *libhttperr = NULL; + switch (request_type) { + case HTTP_REQ_TYPE_HEAD: + if ((http_head_request(ps->hps, ps->url.abspath)) != 0) { + while ((errcode = http_get_lasterr(ps->hps, + &errsrc)) != 0) { + /* Have an error - is it EINTR? */ + if (errsrc == ERRSRC_SYSTEM) { + my_errno = errcode; + break; + } else if (libhttperr == NULL) { + /* save first non-system error message */ + libhttperr = + http_errorstr(errsrc, errcode); + } + } + switch (my_errno) { + case EINTR: + case ETIMEDOUT: + /* Timed out. Try, try again */ + ret = WEB_TIMEOUT; + break; + case ECONNREFUSED: + ret = WEB_CONNREFUSED; + break; + case EHOSTDOWN: + ret = WEB_HOSTDOWN; + break; + default: + /* some other fatal error */ + ret = WEB_NOCONNECT; + if (libhttperr == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_INIT_CONN), + ps->url.hport.hostname); + } else { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTP), libhttperr); + } + break; + } + goto cleanup; + } + break; + + case HTTP_REQ_TYPE_GET: + if (cp && ep) { + if (http_get_range_request(ps->hps, ps->url.abspath, + cp, ep - cp) != 0) { + while ((errcode = http_get_lasterr(ps->hps, + &errsrc)) != 0) { + /* Have an error - is it EINTR? */ + if (errsrc == ERRSRC_SYSTEM) { + my_errno = errcode; + break; + } else { + /* + * save first non-system + * error message + */ + libhttperr = + http_errorstr(errsrc, + errcode); + } + } + switch (my_errno) { + case EINTR: + case ETIMEDOUT: + /* Timed out. Try, try again */ + ret = WEB_TIMEOUT; + break; + case ECONNREFUSED: + ret = WEB_CONNREFUSED; + break; + case EHOSTDOWN: + ret = WEB_HOSTDOWN; + break; + default: + /* some other fatal error */ + ret = WEB_NOCONNECT; + if (libhttperr == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_INIT_CONN), + ps->url.hport.hostname); + } else { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTP), + libhttperr); + } + break; + } + goto cleanup; + } + + if (!web_eval_headers(err)) { + ret = WEB_NOCONNECT; + goto cleanup; + } + } else { + if ((http_get_request(ps->hps, ps->url.abspath)) + != 0) { + while ((errcode = http_get_lasterr(ps->hps, + &errsrc)) != 0) { + /* Have an error - is it EINTR? */ + if (errsrc == ERRSRC_SYSTEM) { + my_errno = errcode; + break; + } else { + /* + * save the first non-system + * error message + */ + libhttperr = + http_errorstr(errsrc, + errcode); + } + } + switch (my_errno) { + case EINTR: + case ETIMEDOUT: + /* Timed out. Try, try again */ + ret = WEB_TIMEOUT; + break; + case ECONNREFUSED: + ret = WEB_CONNREFUSED; + break; + case EHOSTDOWN: + ret = WEB_HOSTDOWN; + break; + default: + /* some other fatal error */ + ret = WEB_NOCONNECT; + if (libhttperr == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_INIT_CONN), + ps->url.hport.hostname); + } else { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTP), + libhttperr); + } + break; + } + goto cleanup; + } + + if (!web_eval_headers(err)) { + ret = WEB_NOCONNECT; + goto cleanup; + } + } + break; + default: + pkgerr_add(err, PKGERR_INTERNAL, gettext(ERR_PKG_INTERNAL), + __FILE__, __LINE__); + } + +cleanup: + return (ret); +} + +/* + * Name: web_eval_headers + * Description: Evaluates HTTP headers returned during an HTTP request. + * This must be called before calling + * http_get_header_value(). + * + * Arguments: err - where to record any errors. + * + * Returns : B_TRUE - success, B_FALSE otherwise + */ +static boolean_t +web_eval_headers(PKG_ERR *err) +{ + const char *http_err; + ulong_t herr; + uint_t errsrc; + + if (http_process_headers(ps->hps, &ps->resp) != 0) { + if ((ps->resp != NULL) && (ps->resp->statusmsg != NULL)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_HTTP), + ps->resp->statusmsg); + } + + herr = http_get_lasterr(ps->hps, &errsrc); + http_err = http_errorstr(errsrc, herr); + pkgerr_add(err, PKGERR_WEB, gettext(ERR_HTTP), + http_err); + return (B_FALSE); + } + return (B_TRUE); +} + +/* + * Name: web_get_file + * Description: Downloads the file URL from the website, all of + * which are recorded in the static 'ps' struct + * + * Arguments: err - where to record any errors. + * dwnld_dir - Directory to download file into + * device - Where to store path to resulting + * file + * nointeract - if non-zero, do not output + * progress + * fname - name of downloaded file link in the dwnld_dir + * + * Returns : WEB_OK - download successful + * WEB_CONNREFUSED - Connection was refused to web site + * WEB_HOSTDOWN - Host was not responding to request + * WEB_GET_FAIL - Unable to initialize download + * state (temp file creation, header parsing, etc) + * WEB_NOCONNECT - Some other connection failure + */ +static WebStatus +web_get_file(PKG_ERR *err, char *dwnld_dir, int nointeract, char **fname) +{ + int i, fd; + int n = 0; + ulong_t abs_pos = 0; + char *head_val = NULL; + char *lastmod_val = NULL; + char *bname = NULL; + struct stat status; + WebStatus ret = WEB_OK; + WebStatus req_ret; + ulong_t errcode; + uint_t errsrc; + int my_errno = 0; + const char *libhttperr = NULL; + char *disp; + char tmp_file[PATH_MAX]; + int len; + + ps->data.prev_cont_length = + ps->data.content_length = + ps->data.cur_pos = 0; + + if ((head_val = http_get_header_value(ps->hps, + CONTENT_LENGTH_HDR)) != NULL) { + ps->data.content_length = atol(head_val); + } else { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_NO_HEAD_VAL), + CONTENT_LENGTH_HDR); + ret = WEB_GET_FAIL; + goto cleanup; + } + + free(head_val); + head_val = NULL; + + if ((head_val = http_get_header_value(ps->hps, + CONTENT_DISPOSITION_HDR)) != NULL) { + /* "inline; parm=val; parm=val */ + if ((disp = strtok(head_val, "; \t\n\f\r")) != NULL) { + /* disp = "inline" */ + while ((disp = strtok(NULL, "; \t\n\f\r")) != NULL) { + /* disp = "parm=val" */ + if (ci_strneq(disp, "filename=", 9)) { + bname = xstrdup(basename(disp + 9)); + trim(bname); + dequote(bname); + } + } + } + free(head_val); + head_val = NULL; + } + + if (bname == NULL) { + /* + * couldn't determine filename from header value, + * so take basename of URL + */ + if ((bname = get_endof_string(ps->url.abspath, '/')) == NULL) { + /* URL is bad */ + pkgerr_add(err, PKGERR_PARSE, + gettext(ERR_PARSE_URL), ps->url.abspath); + ret = WEB_GET_FAIL; + goto cleanup; + } + } + + *fname = bname; + + if ((head_val = http_get_header_value(ps->hps, LAST_MODIFIED_HDR)) + != NULL) { + + if ((lastmod_val = condense_lastmodified(head_val)) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_BAD_HEAD_VAL), + LAST_MODIFIED_HDR, head_val); + ret = WEB_GET_FAIL; + goto cleanup; + } + free(head_val); + head_val = NULL; + + if ((ps->uniqfile = get_unique_filename(dwnld_dir, + lastmod_val)) == NULL) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPEN_TMP)); + ret = WEB_GET_FAIL; + goto cleanup; + } + + free(lastmod_val); + lastmod_val = NULL; + + if ((fd = open(ps->uniqfile, + O_NONBLOCK|O_RDWR|O_APPEND|O_CREAT|O_EXCL, + 640)) == -1) { + + /* + * A partial downloaded file + * already exists, so open it. + */ + if ((fd = open(ps->uniqfile, + O_NONBLOCK|O_RDWR|O_APPEND)) != -1) { + if (fstat(fd, &status) == -1 || + !S_ISREG(status.st_mode)) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_DWNLD_NO_CONT), + ps->uniqfile); + ret = WEB_GET_FAIL; + goto cleanup; + } else { + echo_out(nointeract, + gettext(MSG_DWNLD_PART), + ps->uniqfile, + status.st_size); + ps->data.prev_cont_length = + status.st_size; + } + } else { + /* unable to open partial file */ + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_DWNLD_NO_CONT), + ps->uniqfile); + ret = WEB_GET_FAIL; + goto cleanup; + } + } + } else { + /* + * no "Last-Modified" header, so this file is not eligible for + * spooling and "resuming last download" operations + */ + ps->spool = B_FALSE; + + /* mkstemp replaces XXXXXX with a unique string */ + if (((len = snprintf(tmp_file, PATH_MAX, + "%s/%sXXXXXX", dwnld_dir, "stream")) < 0) || + (len >= PATH_MAX)) { + pkgerr_add(err, PKGERR_WEB, + gettext(MSG_NOTEMP), dwnld_dir); + ret = WEB_GET_FAIL; + goto cleanup; + } + + if ((fd = mkstemp(tmp_file)) == -1) { + pkgerr_add(err, PKGERR_WEB, + gettext(MSG_NOTMPFIL), tmp_file); + ret = WEB_GET_FAIL; + goto cleanup; + } + + if (fstat(fd, &status) == -1 || + !S_ISREG(status.st_mode)) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_DWNLD_NO_CONT), + ps->uniqfile); + ret = WEB_GET_FAIL; + goto cleanup; + } + + ps->data.prev_cont_length = 0; + ps->uniqfile = xstrdup(tmp_file); + } + + /* File has already been completely downloaded */ + if (ps->data.prev_cont_length == ps->data.content_length) { + echo_out(nointeract, gettext(MSG_DWNLD_PREV), ps->uniqfile); + ps->data.cur_pos = ps->data.prev_cont_length; + if (!make_link(dwnld_dir, bname)) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), + dwnld_dir); + ret = WEB_GET_FAIL; + goto cleanup; + } + /* we're done, so cleanup and return success */ + goto cleanup; + } else if (ps->data.prev_cont_length != 0) { + ps->data.cur_pos = ps->data.prev_cont_length; + } + + if (!ck_dwnld_dir_space(err, dwnld_dir, + (ps->data.prev_cont_length != 0) ? + (ps->data.content_length - ps->data.cur_pos) : + ps->data.content_length)) { + ret = WEB_GET_FAIL; + goto cleanup; + } + + if ((req_ret = web_send_request(err, HTTP_REQ_TYPE_GET, + ps->data.cur_pos, ps->data.content_length)) != WEB_OK) { + ret = req_ret; + goto cleanup; + } + + if (ps->data.prev_cont_length != 0) + echo_out(nointeract, gettext(MSG_DWNLD_CONT)); + else + echo_out(nointeract, gettext(MSG_DWNLD)); + + progress_setup(nointeract, ps->data.content_length); + + /* Download the file a BLOCK at a time */ + while (ps->data.cur_pos < ps->data.content_length) { + progress_report(nointeract, abs_pos); + i = ((ps->data.content_length - ps->data.cur_pos) < BLOCK) ? + (ps->data.content_length - ps->data.cur_pos) + : BLOCK; + if ((n = http_read_body(ps->hps, ps->content, i)) <= 0) { + while ((errcode = http_get_lasterr(ps->hps, + &errsrc)) != 0) { + /* Have an error - is it EINTR? */ + if (errsrc == ERRSRC_SYSTEM) { + my_errno = errcode; + break; + } else { + /* + * save first non-system + * error message + */ + libhttperr = + http_errorstr(errsrc, errcode); + } + } + switch (my_errno) { + case EINTR: + case ETIMEDOUT: + /* Timed out. Try, try again */ + ret = WEB_TIMEOUT; + break; + case ECONNREFUSED: + ret = WEB_CONNREFUSED; + break; + case EHOSTDOWN: + ret = WEB_HOSTDOWN; + break; + default: + /* some other fatal error */ + ret = WEB_NOCONNECT; + if (libhttperr == NULL) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_INIT_CONN), + ps->url.hport.hostname); + } else { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_HTTP), libhttperr); + } + break; + } + goto cleanup; + } + if ((n = write(fd, ps->content, n)) == 0) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_WRITE), + ps->uniqfile, strerror(errno)); + ret = WEB_GET_FAIL; + goto cleanup; + } + ps->data.cur_pos += n; + abs_pos += n; + } + + progress_finish(nointeract); + echo_out(nointeract, gettext(MSG_DWNLD_COMPLETE)); + + if (!make_link(dwnld_dir, bname)) { + pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), + dwnld_dir); + ret = WEB_GET_FAIL; + goto cleanup; + } + +cleanup: + sync(); + if (fd != -1) { + (void) close(fd); + } + + if (head_val != NULL) + free(head_val); + + if (lastmod_val != NULL) + free(lastmod_val); + + return (ret); +} + +/* + * Name: make_link + * Description: Create new link to file being downloaded + * + * Arguments: dwnld_dir - directory in which downloaded file exists + * bname - name of link + * + * Returns : B_TRUE - success, B_FALSE otherwise + */ +static boolean_t +make_link(char *dwnld_dir, char *bname) +{ + int len; + + if ((ps->link = (char *)xmalloc(PATH_MAX)) == NULL) + return (B_FALSE); + if (((len = snprintf(ps->link, PATH_MAX, "%s/%s", + dwnld_dir, bname)) < 0) || + len >= PATH_MAX) + return (B_FALSE); + + (void) link(ps->uniqfile, ps->link); + + return (B_TRUE); +} + +/* + * Name: get_startof_string + * Description: searches string for token, returns a newly-allocated + * substring of the given string up to, but not + * including, token. for example + * get_startof_string("abcd", 'c') will return "ab" + * + * Arguments: path - path to split + * token - character to split on + * + * Returns : substring of 'path', up to, but not including, + * token, if token appears in path. Otherwise, + * returns NULL. + */ +char * +get_startof_string(char *path, char token) +{ + char *p, *p2; + + if (path == NULL) + return (NULL); + + p = xstrdup(path); + + p2 = strchr(p, token); + if (p2 == NULL) { + free(p); + return (NULL); + } else { + *p2 = '\0'; + return (p); + } +} + +/* + * Name: get_endof_string + * Description: searches string for token, returns a + * newly-allocated substring of the given string, + * starting at character following token, to end of + * string. + * + * for example get_end_string("abcd", 'c') + * will return "d" + * + * Arguments: path - path to split + * token - character to split on + * + * Returns : substring of 'path', beginning at character + * following token, to end of string, if + * token appears in path. Otherwise, + * returns NULL. + */ +char * +get_endof_string(char *path, char token) +{ + char *p, *p2; + + if (path == NULL) + return (NULL); + + p = xstrdup(path); + + if ((p2 = strrchr(p, token)) == NULL) { + return (NULL); + } + + return (p2 + 1); +} + +/* + * Name: progress_setup + * Description: Initialize session for reporting progress + * + * Arguments: nointeract - if non-zero, do not do anything + * ulong_t - size of job to report progress for + * + * Returns : none + */ +static void +progress_setup(int nointeract, ulong_t size_of_load) +{ + ulong_t divisor; + ulong_t term_width = TERM_WIDTH; + + if (nointeract) + return; + + if (size_of_load > MED_DWNLD && size_of_load < LARGE_DWNLD) + divisor = MED_DIVISOR; + else if (size_of_load > LARGE_DWNLD) { + term_width = TERM_WIDTH - 8; + divisor = LARGE_DIVISOR; + } else + divisor = SMALL_DIVISOR; + + const_increment = size_of_load / term_width; + const_divider = size_of_load / divisor; + const_completed = 100 / divisor; +} + +/* + * Name: progress_report + * Description: Report progress for current progress context, + * to stderr + * + * Arguments: nointeract - if non-zero, do not do anything + * position - how far along in the job to report. + * This should be <= size used during progress_setup + * + * Returns : none + */ +static void +progress_report(int nointeract, ulong_t position) +{ + static ulong_t increment; + static ulong_t divider; + + if (nointeract) + return; + + if (position == 0) { + increment = const_increment; + divider = const_divider; + } + if (position > increment && position < divider) { + (void) putc('.', stderr); + increment += const_increment; + } else if (position > divider) { + completed += const_completed; + (void) fprintf(stderr, "%ld%c", completed, '%'); + increment += const_increment; + divider += const_divider; + } +} + +/* + * Name: progress_finish + * Description: Finalize session for reporting progress. + * "100%" is reported to screen + * + * Arguments: nointeract - if non-zero, do not do anything + * + * Returns : none + */ +static void +progress_finish(int nointeract) +{ + if (nointeract) + return; + + (void) fprintf(stderr, "%d%c\n", 100, '%'); +} + +/* + * Name: init_session + * Description: Initializes static 'ps' structure with default + * values + * + * Arguments: none + * + * Returns : B_TRUE - success, B_FALSE otherwise + */ +static boolean_t +init_session(void) +{ + if ((ps = (WEB_SESSION *) + xmalloc(sizeof (WEB_SESSION))) == NULL) { + return (B_FALSE); + } + (void) memset(ps, 0, sizeof (*ps)); + + if ((ps->content = (char *)xmalloc(BLOCK)) == NULL) { + return (B_FALSE); + } + + (void) memset(ps->content, 0, BLOCK); + + ps->data.cur_pos = 0UL; + ps->data.content_length = 0UL; + ps->url.https = B_FALSE; + ps->uniqfile = NULL; + ps->link = NULL; + ps->dwnld_dir = NULL; + ps->spool = B_TRUE; + ps->errstr = NULL; + ps->keystore = NULL; + + return (B_TRUE); +} + +/* + * Name: ck_downld_dir_space + * Description: Verify enough space exists in directory to hold file + * + * Arguments: err - where to record any errors. + * dwnld_dir - Directory to check available space in + * bytes_needed - How many bytes are need + * + * Returns : B_TRUE - enough space exists in dwnld_dir to hold + * bytes_needed bytes, otherwise B_FALSE + */ +static boolean_t +ck_dwnld_dir_space(PKG_ERR *err, char *dwnld_dir, ulong_t bytes_needed) +{ + u_longlong_t bytes_avail; + u_longlong_t block_pad; + struct statvfs64 status; + + if (statvfs64(dwnld_dir, &status)) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_TMPDIR), dwnld_dir); + return (B_FALSE); + } + + block_pad = (status.f_frsize ? status.f_frsize : status.f_bsize); + bytes_avail = status.f_bavail * block_pad; + + if ((((u_longlong_t)bytes_needed) + block_pad) > bytes_avail) { + pkgerr_add(err, PKGERR_WEB, gettext(ERR_DISK_SPACE), + dwnld_dir, + (((u_longlong_t)bytes_needed) + block_pad) / 1024ULL, + bytes_avail / 1024ULL); + return (B_FALSE); + } + + return (B_TRUE); +} + +/* + * Description: + * This function returns a unique file name based on the parts of the + * URI. This is done to enable partially downloaded files to be resumed. + * Arguments: + * dir - The directory that should contain the filename. + * last_modified - A string representing the date of last modification, + * used as part of generating unique name + * Returns: + * A valid filename or NULL. + */ + +static char * +get_unique_filename(char *dir, char *last_modified) +{ + char *buf, *buf2, *beg_str; + int len; + + if ((buf = (char *)xmalloc(PATH_MAX)) == NULL) { + return (NULL); + } + if ((buf2 = (char *)xmalloc(PATH_MAX)) == NULL) { + return (NULL); + } + + /* prepare strings for being cat'ed onto */ + buf[0] = buf2[0] = '\0'; + /* + * No validation of the path is done here. We just construct the path + * and it must be validated later + */ + + if (dir) { + if (((len = snprintf(buf2, PATH_MAX, "%s/", dir)) < 0) || + (len >= PATH_MAX)) + return (NULL); + } else { + return (NULL); + } + + if (ps->url.abspath) + if (strlcat(buf, ps->url.abspath, PATH_MAX) >= PATH_MAX) + return (NULL); + if (ps->url.hport.hostname) + if (isdigit((int)ps->url.hport.hostname[0])) { + if (strlcat(buf, ps->url.hport.hostname, PATH_MAX) + >= PATH_MAX) + return (NULL); + } else { + if ((beg_str = + get_startof_string(ps->url.hport.hostname, '.')) + != NULL) + if (strlcat(buf, beg_str, PATH_MAX) >= PATH_MAX) + return (NULL); + } + if (last_modified != NULL) + if (strlcat(buf, last_modified, PATH_MAX) >= PATH_MAX) + return (NULL); + + if ((buf = replace_token(buf, '/', '_')) != NULL) { + if (strlcat(buf2, buf, PATH_MAX) >= PATH_MAX) { + return (NULL); + } else { + if (buf) free(buf); + return (buf2); + } + } else { + if (buf) free(buf); + if (buf2) free(buf2); + return (NULL); + } +} + +/* + * Description: + * Removes token(s) consisting of one character from any path. + * Arguments: + * path - The path to search for the token in. + * token - The token to search for + * Returns: + * The path with all tokens removed or NULL. + */ +static char * +replace_token(char *path, char oldtoken, char newtoken) +{ + char *newpath, *p; + + if ((path == NULL) || (oldtoken == '\0') || (newtoken == '\0')) { + return (NULL); + } + + newpath = xstrdup(path); + + for (p = newpath; *p != '\0'; p++) { + if (*p == oldtoken) { + *p = newtoken; + } + } + + return (newpath); +} + +/* + * Name: trim + * Description: Trims whitespace from a string + * has been registered) + * Scope: private + * Arguments: string - string to trim. It is assumed + * this string is writable up to it's entire + * length. + * Returns: none + */ +static void +trim(char *str) +{ + int len, i; + if (str == NULL) { + return; + } + + len = strlen(str); + /* strip from front */ + while (isspace(*str)) { + for (i = 0; i < len; i++) { + str[i] = str[i+1]; + } + } + + /* strip from back */ + len = strlen(str); + while (isspace(str[len-1])) { + len--; + } + str[len] = '\0'; +} + +/* + * Description: + * Resolves double quotes + * Arguments: + * str - The string to resolve + * Returns: + * None + */ +static void +dequote(char *str) +{ + char *cp; + + if ((str == NULL) || (str[0] != '"')) { + /* no quotes */ + return; + } + + /* remove first quote */ + memmove(str, str + 1, strlen(str) - 1); + + /* + * scan string looking for ending quote. + * escaped quotes like \" don't count + */ + cp = str; + + while (*cp != '\0') { + switch (*cp) { + case '\\': + /* found an escaped character */ + /* make sure end of string is not '\' */ + if (*++cp != '\0') { + cp++; + } + break; + + case '"': + *cp = '\0'; + break; + default: + cp++; + } + } +} + +/* + * Name: get_ENV_proxy + * Description: Retrieves setting of proxy env variable + * + * Arguments: err - where to record any errors. + * proxy - where to store proxy + * + * Returns : B_TRUE - http proxy was found and valid, stored in proxy + * B_FALSE - error, errors recorded in err + */ +static boolean_t +get_ENV_proxy(PKG_ERR *err, char **proxy) +{ + char *buf; + + if ((buf = getenv("HTTPPROXY")) != NULL) { + if (!path_valid(buf)) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_ILL_ENV), "HTTPPROXY", buf); + return (B_FALSE); + } else { + *proxy = buf; + return (B_TRUE); + } + } else { + /* try the other env variable */ + if ((buf = getenv("http_proxy")) != NULL) { + if (!path_valid(buf)) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_ILL_ENV), "http_proxy", buf); + return (B_FALSE); + } + if (!strneq(buf, "http://", 7)) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_ILL_ENV), "http_proxy", buf); + return (B_FALSE); + } + + /* skip over the http:// part of the proxy "url" */ + *proxy = buf + 7; + return (B_TRUE); + } + } + + /* either the env variable(s) were set and valid, or not set */ + return (B_TRUE); +} + +/* + * Name: get_ENV_proxyport + * Description: Retrieves setting of PROXYPORT env variable + * + * Arguments: err - where to record any errors. + * port - where to store resulting port + * + * Returns : B_TRUE - string found in PROXYPORT variable, converted + * to decimal integer, if it exists + * and is valid. Or, PROXYPORT not set, port set to 1. + * B_FALSE - env variable set, but invalid + * (not a number for example) + */ +static boolean_t +get_ENV_proxyport(PKG_ERR *err, ushort_t *port) +{ + char *buf; + ushort_t newport; + buf = getenv("HTTPPROXYPORT"); + if (buf != NULL) { + if (!path_valid(buf)) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_ILL_ENV), "HTTPPROXYPORT", buf); + return (B_FALSE); + } + if ((newport = atoi(buf)) == 0) { + pkgerr_add(err, PKGERR_WEB, + gettext(ERR_ILL_ENV), "HTTPPROXYPORT", buf); + return (B_FALSE); + } + *port = newport; + return (B_TRUE); + } else { + *port = 1; + return (B_TRUE); + } +} + +/* + * Name: remove_dwnld_file + * Description: Removes newly-downloaded file if completely downloaded. + * + * Arguments: path - path to file to remove + * + * Returns : B_TRUE - success, B_FALSE otherwise + * if it's '0' (not OK) we simply return it, since the + * verification operation has already determined that the + * cert is invalid. if 'ok' is non-zero, then we do our + * checks, and return 0 or 1 based on if the cert is + * invalid or valid. + */ +static boolean_t +remove_dwnld_file(char *path) +{ + if (path && path != NULL) { + /* + * Only remove the downloaded file if it has been completely + * downloaded, or is not eligible for spooling + */ + if ((!ps->spool) || + (ps->data.cur_pos >= ps->data.content_length)) { + (void) unlink(path); + } + } else { + return (B_FALSE); + } + return (B_TRUE); +} + +/* + * Name: condense_lastmodifided + * Description: generates a substring of a last-modified string, + * and removes colons. + * + * Arguments: last_modified - string of the form + * "Wed, 23 Oct 2002 21:59:45 GMT" + * + * Returns : + * new string, consisting of hours/minutes/seconds only, + * sans any colons. + */ +char * +condense_lastmodified(char *last_modified) +{ + char *p, *p2; + + /* + * Last-Modified: Wed, 23 Oct 2002 21:59:45 GMT + * Strip the hours, minutes and seconds, without the ':'s, from + * the above string, void of the ':". + */ + + if (last_modified == NULL) + return (NULL); + + if ((p = xstrdup(last_modified)) == NULL) + return (NULL); + p2 = (strstr(p, ":") - 2); + p2[8] = '\0'; + return (replace_token(p2, ':', '_')); +} + +/* + * Name: backoff + * Description: sleeps for a certain # of seconds after a network + * failure. + * Scope: public + * Arguments: none + * Returns: none + */ +void +backoff() +{ + static boolean_t initted = B_FALSE; + int backoff; + long seed; + + if (!initted) { + /* seed the rng */ + (void) _get_random_info(&seed, sizeof (seed)); + srand48(seed); + initted = B_TRUE; + } + + backoff = drand48() * (double)cur_backoff; + (void) sleep(backoff); + if (cur_backoff < MAX_BACKOFF) { + /* + * increase maximum time we might wait + * next time so as to fall off over + * time. + */ + cur_backoff *= BACKOFF_FACTOR; + } +} + +/* + * Name: reset_backoff + * Description: notifies the backoff service that whatever was + * being backoff succeeded. + * Scope: public + * Arguments: none + * Returns: none + */ +void +reset_backoff() +{ + cur_backoff = MIN_BACKOFF; +} + +/* + * Name: _get_random_info + * Description: generate an amount of random bits. Currently + * only a small amount (a long long) can be + * generated at one time. + * Scope: private + * Arguments: buf - [RO, *RW] (char *) + * Buffer to copy bits into + * size - amount to copy + * Returns: B_TRUE on success, B_FALSE otherwise. The buffer is filled + * with the amount of bytes of random data specified. + */ +static boolean_t +_get_random_info(void *buf, int size) +{ + struct timeval tv; + typedef struct { + long low_time; + long hostid; + } randomness; + randomness r; + + /* if the RANDOM file exists, use it */ + if (access(RANDOM, R_OK) == 0) { + if ((RAND_load_file(RANDOM, 1024 * 1024)) > 0) { + if (RAND_bytes((uchar_t *)buf, size) == 1) { + /* success */ + return (B_TRUE); + } + } + } + + /* couldn't use RANDOM file, so fallback to time of day and hostid */ + (void) gettimeofday(&tv, (struct timezone *)0); + + /* Wouldn't it be nice if we could hash these */ + r.low_time = tv.tv_usec; + r.hostid = gethostid(); + + if (sizeof (r) < size) { + /* + * Can't copy correctly + */ + return (B_FALSE); + } + (void) memcpy(buf, &r, size); + return (B_TRUE); +} + +/* + * Name: pkg_passphrase_cb + * Description: Default callback that applications can use when + * a passphrase is needed. This routine collects + * a passphrase from the user using the given + * passphrase retrieval method set with + * set_passphrase_passarg(). If the method + * indicates an interactive prompt, then the + * prompt set with set_passphrase_prompt() + * is displayed. + * + * Arguments: buf - Buffer to copy passphrase into + * size - Max amount to copy to buf + * rw - Whether this passphrase is needed + * to read something off disk, or + * write something to disk. Applications + * typically want to ask twice when getting + * a passphrase for writing something. + * data - application-specific data. In this + * callback, data is a pointer to + * a keystore_passphrase_data structure. + * + * Returns: Length of passphrase collected, or -1 on error. + * Errors recorded in 'err' object in the *data. + */ +int +pkg_passphrase_cb(char *buf, int size, int rw, void *data) +{ + BIO *pwdbio = NULL; + char passphrase_copy[MAX_PHRASELEN + 1]; + PKG_ERR *err; + int passlen; + char *ws; + char prompt_copy[MAX_VERIFY_MSGLEN]; + char *passphrase; + char *arg; + + err = ((keystore_passphrase_data *)data)->err; + + if (passarg == NULL) { + arg = "console"; + } else { + arg = passarg; + } + + /* default method of collecting password is by prompting */ + if (ci_streq(arg, "console")) { + if ((passphrase = getpassphrase(prompt)) == NULL) { + pkgerr_add(err, PKGERR_BADPASS, + gettext(MSG_NOPASS), arg); + return (-1); + } + + if (rw) { + /* + * if the password is being supplied for + * writing something to disk, verify it first + */ + + /* make a copy (getpassphrase overwrites) */ + strlcpy(passphrase_copy, passphrase, + MAX_PHRASELEN + 1); + + if (((passlen = snprintf(prompt_copy, + MAX_VERIFY_MSGLEN, "%s: %s", + gettext(MSG_PASSWD_AGAIN), + prompt)) < 0) || + (passlen >= (MAX_PHRASELEN + 1))) { + pkgerr_add(err, PKGERR_BADPASS, + gettext(MSG_NOPASS), arg); + return (-1); + } + + if ((passphrase = + getpassphrase(prompt_copy)) == NULL) { + pkgerr_add(err, PKGERR_BADPASS, + gettext(MSG_NOPASS), arg); + return (-1); + } + + if (!streq(passphrase_copy, passphrase)) { + pkgerr_add(err, PKGERR_READ, + gettext(MSG_PASSWD_NOMATCH)); + return (-1); + } + } + } else if (ci_strneq(arg, "pass:", 5)) { + passphrase = arg + 5; + } else if (ci_strneq(arg, "env:", 4)) { + passphrase = getenv(arg + 4); + } else if (ci_strneq(arg, "file:", 5)) { + + /* open file for reading */ + if ((pwdbio = BIO_new_file(arg + 5, "r")) == NULL) { + pkgerr_add(err, PKGERR_EXIST, + gettext(MSG_PASSWD_FILE), arg + 5); + return (-1); + } + + /* read first line */ + if (((passlen = BIO_gets(pwdbio, buf, size)) < 1) || + (passlen > size)) { + pkgerr_add(err, PKGERR_READ, gettext(MSG_PASSWD_FILE), + arg + 5); + return (-1); + } + BIO_free_all(pwdbio); + pwdbio = NULL; + + if (passlen == size) { + /* + * password was maximum length, so there is + * no null terminator. null-terminate it + */ + buf[size - 1] = '\0'; + } + + /* first newline found is end of passwd, so nuke it */ + if ((ws = strchr(buf, '\n')) != NULL) { + *ws = '\0'; + } + return (strlen(buf)); + } else { + /* unrecognized passphrase */ + pkgerr_add(err, PKGERR_BADPASS, + gettext(MSG_BADPASSARG), arg); + return (-1); + } + + if (passphrase == NULL) { + /* unable to collect passwd from given source */ + pkgerr_add(err, PKGERR_BADPASS, + gettext(MSG_NOPASS), arg); + return (-1); + } + + strlcpy(buf, passphrase, size); + return (strlen(buf)); +} diff --git a/usr/src/lib/libpkg/common/pkgweb.h b/usr/src/lib/libpkg/common/pkgweb.h new file mode 100644 index 0000000000..fe9dc372d9 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgweb.h @@ -0,0 +1,130 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _PKGWEB_H +#define _PKGWEB_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <netdb.h> +#include <boot_http.h> + +/* shortest backoff delay possible (in seconds) */ +#define MIN_BACKOFF 1 + +/* how much to increase backoff time after each failure */ +#define BACKOFF_FACTOR 2 + +/* Maximum amount of backoff for a heavy network or flaky server */ +#define MAX_BACKOFF 128 + +typedef enum { + HTTP_REQ_TYPE_HEAD, + HTTP_REQ_TYPE_GET +} HTTPRequestType; + +typedef enum { + OCSPSuccess, + OCSPMem, + OCSPParse, + OCSPConnect, + OCSPRequest, + OCSPResponder, + OCSPUnsupported, + OCSPVerify, + OCSPInternal, + OCSPNoURI +} OCSPStatus; + +typedef enum { + none, + web_http, + web_https, + web_ftp +} WebScheme; + +typedef enum { + WEB_OK, + WEB_TIMEOUT, + WEB_CONNREFUSED, + WEB_HOSTDOWN, + WEB_VERIFY_SETUP, + WEB_NOCONNECT, + WEB_GET_FAIL +} WebStatus; + +typedef struct { + ulong_t prev_cont_length; + ulong_t content_length; + ulong_t cur_pos; +} DwnldData; + +typedef struct { + keystore_handle_t keystore; + char *certfile; + char *uniqfile; + char *link; + char *errstr; + char *dwnld_dir; + boolean_t spool; + void *content; + int timeout; + url_hport_t proxy; + url_t url; + DwnldData data; + http_respinfo_t *resp; + boot_http_ver_t *http_vers; + http_handle_t *hps; +} WEB_SESSION; + +extern boolean_t web_session_control(PKG_ERR *, char *, char *, + keystore_handle_t, char *, ushort_t, int, int, int, char **); +extern boolean_t get_signature(PKG_ERR *, char *, struct pkgdev *, + PKCS7 **); +extern boolean_t validate_signature(PKG_ERR *, char *, BIO *, PKCS7 *, + STACK_OF(X509) *, url_hport_t *, int); +extern boolean_t ds_validate_signature(PKG_ERR *, struct pkgdev *, char **, + char *, PKCS7 *, STACK_OF(X509) *, url_hport_t *, int); +extern boolean_t get_proxy_port(PKG_ERR *, char **, ushort_t *); +extern boolean_t path_valid(char *); +extern void web_cleanup(void); +extern ushort_t strip_port(char *proxy); +extern void set_web_install(void); +extern int is_web_install(void); +extern void echo_out(int, char *, ...); +extern void backoff(void); +extern void reset_backoff(void); +extern char *get_endof_string(char *, char); +extern char *get_startof_string(char *, char); + +#ifdef __cplusplus +} +#endif + +#endif /* _PKGWEB_H */ diff --git a/usr/src/lib/libpkg/common/pkgxpand.c b/usr/src/lib/libpkg/common/pkgxpand.c new file mode 100644 index 0000000000..7f92aa8420 --- /dev/null +++ b/usr/src/lib/libpkg/common/pkgxpand.c @@ -0,0 +1,118 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "pkglib.h" +#include "pkglocale.h" + +extern char *fpkginst(char *pkg, ...); /* libadm.a */ +extern char *pkgdir; /* WHERE? */ + +#define ispkgalias(p) (*p == '+') +#define LSIZE 512 +#define MALSIZ 16 + +char ** +pkgalias(char *pkg) +{ + FILE *fp; + char path[PATH_MAX], *pkginst; + char *mypkg, *myarch, *myvers, **pkglist; + char line[LSIZE]; + int n, errflg; + + pkglist = (char **)calloc(MALSIZ, sizeof (char *)); + if (pkglist == NULL) + return ((char **)0); + + (void) sprintf(path, "%s/%s/pkgmap", pkgdir, pkg); + if ((fp = fopen(path, "r")) == NULL) + return ((char **)0); + + n = errflg = 0; + while (fgets(line, LSIZE, fp)) { + mypkg = strtok(line, " \t\n"); + myarch = strtok(NULL, "( \t\n)"); + myvers = strtok(NULL, "\n"); + + (void) fpkginst(NULL); + pkginst = fpkginst(mypkg, myarch, myvers); + if (pkginst == NULL) { + logerr( + pkg_gt("no package instance for [%s]"), mypkg); + errflg++; + continue; + } + if (errflg) + continue; + + pkglist[n] = strdup(pkginst); + if ((++n % MALSIZ) == 0) { + pkglist = (char **)realloc(pkglist, + (n+MALSIZ)*sizeof (char *)); + if (pkglist == NULL) + return ((char **)0); + } + } + pkglist[n] = NULL; + + (void) fclose(fp); + if (errflg) { + while (n-- >= 0) + free(pkglist[n]); + free(pkglist); + return ((char **)0); + } + return (pkglist); +} + +#if 0 +char ** +pkgxpand(char *pkg[]) +{ + static int level = 0; + char **pkglist; + int i; + + if (++level >= 0) + printf(pkg_gt("too deep")); + for (i = 0; pkg[i]; i++) { + if (ispkgalias(pkg[i])) { + pkglist = pkgxpand(&pkg[i]); + pkgexpand(pkglist); + } + } +} +#endif /* 0 */ diff --git a/usr/src/lib/libpkg/common/ppkgmap.c b/usr/src/lib/libpkg/common/ppkgmap.c new file mode 100644 index 0000000000..423def1bab --- /dev/null +++ b/usr/src/lib/libpkg/common/ppkgmap.c @@ -0,0 +1,139 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include "pkgstrct.h" + +int holdcinfo = 0; + +int +ppkgmap(struct cfent *ept, FILE *fp) +{ + if (ept->path == NULL) + return (-1); + + if (ept->volno) { + if (fprintf(fp, "%d ", ept->volno) < 0) + return (-1); + } + + if (ept->ftype == 'i') { + if (fprintf(fp, "%c %s", ept->ftype, ept->path) < 0) + return (-1); + } else { + if (fprintf(fp, "%c %s %s", ept->ftype, ept->pkg_class, + ept->path) < 0) + return (-1); + } + + if (ept->ainfo.local) { + if (fprintf(fp, "=%s", ept->ainfo.local) < 0) + return (-1); + } + + if (strchr("cb", ept->ftype)) { +#ifdef SUNOS41 + if (ept->ainfo.xmajor == BADMAJOR) { + if (fprintf(fp, " ?") < 0) + return (-1); + } else { + if (fprintf(fp, " %d", ept->ainfo.xmajor) < 0) + return (-1); + } +#else + if (ept->ainfo.major == BADMAJOR) { + if (fprintf(fp, " ?") < 0) + return (-1); + } else { + if (fprintf(fp, " %d", ept->ainfo.major) < 0) + return (-1); + } +#endif +#ifdef SUNOS41 + if (ept->ainfo.xminor == BADMINOR) { + if (fprintf(fp, " ?") < 0) + return (-1); + } else { + if (fprintf(fp, " %d", ept->ainfo.xminor) < 0) + return (-1); + } +#else + if (ept->ainfo.minor == BADMINOR) { + if (fprintf(fp, " ?") < 0) + return (-1); + } else { + if (fprintf(fp, " %d", ept->ainfo.minor) < 0) + return (-1); + } +#endif + } + + if (strchr("dxcbpfve", ept->ftype)) { + if (fprintf(fp, ((ept->ainfo.mode == BADMODE) ? " ?" : " %04o"), + ept->ainfo.mode) < 0) + return (-1); + if (fprintf(fp, " %s %s", ept->ainfo.owner, ept->ainfo.group) < + 0) + return (-1); + } + if (holdcinfo) { + if (fputc('\n', fp) == EOF) + return (-1); + return (0); + } + + if (strchr("ifve", ept->ftype)) { + if (fprintf(fp, ((ept->cinfo.size == BADCONT) ? " ?" : " %llu"), + ept->cinfo.size) < 0) + return (-1); + if (fprintf(fp, ((ept->cinfo.cksum == BADCONT) ? " ?" : " %ld"), + ept->cinfo.cksum) < 0) + return (-1); + if (fprintf(fp, + ((ept->cinfo.modtime == BADCONT) ? " ?" : " %ld"), + ept->cinfo.modtime) < 0) + return (-1); + } + + if (ept->ftype == 'i') { + if (fputc('\n', fp) == EOF) + return (-1); + return (0); + } + if (fprintf(fp, "\n") < 0) + return (-1); + return (0); +} diff --git a/usr/src/lib/libpkg/common/progerr.c b/usr/src/lib/libpkg/common/progerr.c new file mode 100644 index 0000000000..8feaf4b81b --- /dev/null +++ b/usr/src/lib/libpkg/common/progerr.c @@ -0,0 +1,201 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include "pkglocale.h" +#include "pkgerr.h" + +static char *ProgName = NULL; /* Set via set_prog_name() */ + + +static void +error_and_exit(int error_num) +{ + (void) fprintf(stderr, "%d\n", error_num); + exit(99); +} + +static void (*fatal_err_func)() = &error_and_exit; + +char * +set_prog_name(char *name) +{ + if (name == NULL) + return (NULL); + if ((name = strdup(name)) == NULL) { + (void) fprintf(stderr, + "set_prog_name(): strdup(name) failed.\n"); + exit(1); + } + ProgName = strrchr(name, '/'); + if (!ProgName++) + ProgName = name; + + return (ProgName); +} + +char * +get_prog_name(void) +{ + return (ProgName); +} + + +/*VARARGS*/ +void +progerr(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if (ProgName && *ProgName) + (void) fprintf(stderr, pkg_gt("%s: ERROR: "), ProgName); + else + (void) fprintf(stderr, pkg_gt(" ERROR: ")); + + (void) vfprintf(stderr, fmt, ap); + + va_end(ap); + + (void) fprintf(stderr, "\n"); +} + +void +pkgerr(PKG_ERR *err) +{ + int i; + + for (i = 0; i < pkgerr_num(err); i++) { + progerr("%s", pkgerr_get(err, i)); + } +} + + +/* + * set_memalloc_failure_func() + * Allows an appliation to specify the function to be called when + * a memory allocation function fails. + * Parameters: + * (*alloc_proc)(int) - specifies the function to call if fatal error + * (such as being unable to allocate memory) occurs. + * Return: + * none + * Status: + * Public + */ +void +set_memalloc_failure_func(void (*alloc_proc)(int)) +{ + if (alloc_proc != (void (*)())NULL) + fatal_err_func = alloc_proc; +} + +/* + * xmalloc() + * Alloc 'size' bytes from heap using malloc() + * Parameters: + * size - number of bytes to malloc + * Return: + * NULL - malloc() failure + * void * - pointer to allocated structure + * Status: + * public + */ +void * +xmalloc(size_t size) +{ + void *tmp; + + if ((tmp = (void *) malloc(size)) == NULL) { + fatal_err_func(errno); + return (NULL); + } else + return (tmp); +} + +/* + * xrealloc() + * Calls realloc() with the specfied parameters. xrealloc() + * checks for realloc failures and adjusts the return value + * automatically. + * Parameters: + * ptr - pointer to existing data block + * size - number of bytes additional + * Return: + * NULL - realloc() failed + * void * - pointer to realloc'd structured + * Status: + * public + */ +void * +xrealloc(void *ptr, size_t size) +{ + void *tmp; + + if ((tmp = (void *)realloc(ptr, size)) == (void *)NULL) { + fatal_err_func(errno); + return ((void *)NULL); + } else + return (tmp); +} + +/* + * xstrdup() + * Allocate space for the string from the heap, copy 'str' into it, + * and return a pointer to it. + * Parameters: + * str - string to duplicate + * Return: + * NULL - duplication failed or 'str' was NULL + * char * - pointer to newly allocated/initialized structure + * Status: + * public + */ +char * +xstrdup(char *str) +{ + char *tmp; + + if (str == NULL) + return ((char *)NULL); + + if ((tmp = strdup(str)) == NULL) { + fatal_err_func(errno); + return ((char *)NULL); + } else + return (tmp); +} diff --git a/usr/src/lib/libpkg/common/putcfile.c b/usr/src/lib/libpkg/common/putcfile.c new file mode 100644 index 0000000000..959f2684b8 --- /dev/null +++ b/usr/src/lib/libpkg/common/putcfile.c @@ -0,0 +1,376 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <sys/types.h> +#include "pkgstrct.h" +#include "pkglib.h" + +/* + * Name: putcfile + * Description: Write contents file entry to specified FILE + * Arguments: struct cfent a_ept - data for contents file entry + * FILE *a_fp - FP of file to write contents file entry to + * Notes: This is identical to putcvfpfile() but this function takes a + * stdio FILE* file to write to instead of a VFP_T file. It is + * MUCH slower than putcvfpfile(). + */ + +int +putcfile(struct cfent *a_ept, FILE *a_fp) +{ + struct pinfo *pinfo; + + if (a_ept->ftype == 'i') { + return (0); /* no ifiles stored in contents DB */ + } + + if (a_ept->path == NULL) { + return (-1); /* no path name - no entry to write */ + } + + if (fputs(a_ept->path, a_fp) == EOF) { + return (-1); + } + + if (a_ept->ainfo.local) { + if (putc('=', a_fp) == EOF) { + return (-1); + } + if (fputs(a_ept->ainfo.local, a_fp) == EOF) + return (-1); + } + + if (a_ept->volno) { + if (fprintf(a_fp, " %d", a_ept->volno) < 0) { + return (-1); + } + } + + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (putc(a_ept->ftype, a_fp) == EOF) { + return (-1); + } + + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (fputs(a_ept->pkg_class, a_fp) == EOF) { + return (-1); + } + + if ((a_ept->ftype == 'c') || (a_ept->ftype == 'b')) { + if (a_ept->ainfo.major == BADMAJOR) { + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (putc('?', a_fp) == EOF) { + return (-1); + } + } else { + if (fprintf(a_fp, " %d", a_ept->ainfo.major) < 0) + return (-1); + } + + if (a_ept->ainfo.minor == BADMINOR) { + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (putc('?', a_fp) == EOF) { + return (-1); + } + } else { + if (fprintf(a_fp, " %d", a_ept->ainfo.minor) < 0) + return (-1); + } + } + + if ((a_ept->ftype == 'd') || (a_ept->ftype == 'x') || + (a_ept->ftype == 'c') || (a_ept->ftype == 'b') || + (a_ept->ftype == 'p') || (a_ept->ftype == 'f') || + (a_ept->ftype == 'v') || (a_ept->ftype == 'e')) { + if (fprintf(a_fp, + ((a_ept->ainfo.mode == BADMODE) ? " ?" : " %04o"), + a_ept->ainfo.mode) < 0) + return (-1); + + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (fputs(a_ept->ainfo.owner, a_fp) == EOF) { + return (-1); + } + + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (fputs(a_ept->ainfo.group, a_fp) == EOF) { + return (-1); + } + } + + if ((a_ept->ftype == 'f') || (a_ept->ftype == 'v') || + (a_ept->ftype == 'e')) { + if (fprintf(a_fp, + ((a_ept->cinfo.size == BADCONT) ? " ?" : " %llu"), + a_ept->cinfo.size) < 0) + return (-1); + + if (fprintf(a_fp, + ((a_ept->cinfo.cksum == BADCONT) ? " ?" : " %ld"), + a_ept->cinfo.cksum) < 0) + return (-1); + + if (fprintf(a_fp, + ((a_ept->cinfo.modtime == BADCONT) ? " ?" : " %ld"), + a_ept->cinfo.modtime) < 0) + return (-1); + } + + pinfo = a_ept->pinfo; + while (pinfo) { + if (putc(' ', a_fp) == EOF) { + return (-1); + } + + if (pinfo->status) { + if (fputc(pinfo->status, a_fp) == EOF) { + return (-1); + } + } + + if (fputs(pinfo->pkg, a_fp) == EOF) { + return (-1); + } + + if (pinfo->editflag) { + if (putc('\\', a_fp) == EOF) { + return (-1); + } + } + + if (pinfo->aclass[0]) { + if (putc(':', a_fp) == EOF) { + return (-1); + } + if (fputs(pinfo->aclass, a_fp) == EOF) { + return (-1); + } + } + pinfo = pinfo->next; + } + + if (putc('\n', a_fp) == EOF) { + return (-1); + } + return (0); +} + +/* + * Name: putcvfpfile + * Description: Write contents file entry to specified VFP + * Arguments: struct cfent a_ept - data for contents file entry + * VFP_T *a_vfp - VFP of file to write contents file entry to + * Notes: This is identical to putcfile() but this function takes a + * VFP_T file to write to instead of a stdio FILE file. It is + * MUCH faster tha putcfile(). + */ + +int +putcvfpfile(struct cfent *a_ept, VFP_T *a_vfp) +{ + struct pinfo *pinfo; + + /* contents file does not maintain any 'i' file entries */ + + if (a_ept->ftype == 'i') { + return (0); + } + + /* cannot create an entry if it has no file name */ + + if (a_ept->path == NULL) { + return (-1); + } + + /* + * Format of contents file line could be one of: + * /file=./dir/file s class SUNWxxx + * /file=../dir/file l class SUNWxxx + * /dir d class mode owner group SUNWxxx SUNWyyy + * /devices/name c class major minor mode owner group SUNWxxx + * /file f class mode owner group size cksum modtime SUNWxxx + * /file x class mode owner group SUNWppro + * /file v class mode owner group size cksum modtime SUNWxxx + * /file e class mode owner group size cksum modtime SUNWxxx + * The package name could be prefixed by one of the following + * status indicators: +-*!%@#~ + */ + + /* + * Adding an entry to the specified VFP. During normal processing the + * contents file is copied to a temporary contents file and entries are + * added as appropriate. When this processing is completed, a decision + * is made on whether or not to overwrite the real contents file with + * the contents of the temporary contents file. If the temporary + * contents file is just a copy of the real contents file then there is + * no need to overwrite the real contents file with the contents of the + * temporary contents file. This decision is made in part on whether + * or not any new or modified entries have been added to the temporary + * contents file. Set the "data is modified" indication associated + * with this VFP so that the real contents file is overwritten when + * processing is done. + */ + + (void) vfpSetModified(a_vfp); + + /* write initial path [all entries] */ + + vfpPuts(a_vfp, a_ept->path); + + /* if link, write out '=' portion */ + + if (a_ept->ainfo.local) { + vfpPutc(a_vfp, '='); + vfpPuts(a_vfp, a_ept->ainfo.local); + } + + /* if volume, write it out */ + + if (a_ept->volno) { + vfpPutc(a_vfp, ' '); + vfpPutInteger(a_vfp, a_ept->volno); + } + + /* write out <space><entry type><space>class> */ + + vfpPutc(a_vfp, ' '); + vfpPutc(a_vfp, a_ept->ftype); + vfpPutc(a_vfp, ' '); + vfpPuts(a_vfp, a_ept->pkg_class); + + /* if char/block device, write out major/minor numbers */ + + if ((a_ept->ftype == 'c') || (a_ept->ftype == 'b')) { + /* major device number */ + if (a_ept->ainfo.major == BADMAJOR) { + vfpPutc(a_vfp, ' '); + vfpPutc(a_vfp, '?'); + } else { + vfpPutc(a_vfp, ' '); + vfpPutInteger(a_vfp, a_ept->ainfo.major); + } + + /* minor device number */ + if (a_ept->ainfo.minor == BADMINOR) { + vfpPutc(a_vfp, ' '); + vfpPutc(a_vfp, '?'); + } else { + vfpPutc(a_vfp, ' '); + vfpPutInteger(a_vfp, a_ept->ainfo.minor); + } + } + + /* if dxcbpfve, write out mode, owner, group */ + + if ((a_ept->ftype == 'd') || (a_ept->ftype == 'x') || + (a_ept->ftype == 'c') || (a_ept->ftype == 'b') || + (a_ept->ftype == 'p') || (a_ept->ftype == 'f') || + (a_ept->ftype == 'v') || (a_ept->ftype == 'e')) { + + /* mode */ + vfpPutFormat(a_vfp, + ((a_ept->ainfo.mode == BADMODE) ? " ?" : " %04o"), + a_ept->ainfo.mode); + + /* owner */ + vfpPutc(a_vfp, ' '); + vfpPuts(a_vfp, a_ept->ainfo.owner); + + /* group */ + vfpPutc(a_vfp, ' '); + vfpPuts(a_vfp, a_ept->ainfo.group); + } + /* if f/v/e, write out size, cksum, modtime */ + + if ((a_ept->ftype == 'f') || (a_ept->ftype == 'v') || + (a_ept->ftype == 'e')) { + /* size */ + vfpPutFormat(a_vfp, + ((a_ept->cinfo.size == BADCONT) ? " ?" : " %llu"), + a_ept->cinfo.size); + + /* cksum */ + vfpPutFormat(a_vfp, + ((a_ept->cinfo.cksum == BADCONT) ? " ?" : " %ld"), + a_ept->cinfo.cksum); + + /* modtime */ + vfpPutFormat(a_vfp, + ((a_ept->cinfo.modtime == BADCONT) ? " ?" : " %ld"), + a_ept->cinfo.modtime); + } + + /* write out list of all packages referencing this entry */ + + pinfo = a_ept->pinfo; + while (pinfo) { + vfpPutc(a_vfp, ' '); + if (pinfo->status) { + vfpPutc(a_vfp, pinfo->status); + } + + vfpPuts(a_vfp, pinfo->pkg); + + if (pinfo->editflag) { + vfpPutc(a_vfp, '\\'); + } + + if (pinfo->aclass[0]) { + vfpPutc(a_vfp, ':'); + vfpPuts(a_vfp, pinfo->aclass); + } + pinfo = pinfo->next; + } + + vfpPutc(a_vfp, '\n'); + return (0); +} diff --git a/usr/src/lib/libpkg/common/rrmdir.c b/usr/src/lib/libpkg/common/rrmdir.c new file mode 100644 index 0000000000..baf5d61c19 --- /dev/null +++ b/usr/src/lib/libpkg/common/rrmdir.c @@ -0,0 +1,83 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include "pkglocale.h" +#include "pkglib.h" + +int +rrmdir(char *a_path) +{ + char path[PATH_MAX+13]; + int i; + int status; + + /* + * For some reason, a simple "rm -rf" will remove the contents + * of the directory, but will fail to remove the directory itself + * with "No such file or directory" when running the pkg commands + * under a virtual root via the "chroot" command. This has been + * seen so far only with the `pkgremove' command, and when the + * the directory is NFS mounted from a 4.x server. This should + * probably be revisited at a later time, but for now we'll just + * remove the directory contents first, then the directory. + */ + + /* do not allow removal of all root files via blank path */ + + if ((a_path == NULL) || (*a_path == '\0')) { + (void) fprintf(stderr, + pkg_gt("warning: rrmdir(path==NULL): nothing deleted\n")); + return (0); + } + + /* + * first generate path with slash-star at the end and attempt to remove + * all files first. If successful then remove with just the path only. + */ + + (void) snprintf(path, sizeof (path), "%s/", a_path); + i = e_ExecCmdList(&status, (char **)NULL, (char *)NULL, + "/bin/rm", "rm", "-rf", path, (char *)NULL); + + if (access(a_path, F_OK) == 0) { + i = e_ExecCmdList(&status, (char **)NULL, (char *)NULL, + "/bin/rmdir", "rmdir", a_path, (char *)NULL); + } + + /* return 0 if last command successful, else return 1 */ + return ((i == 0 && status == 0) ? 0 : 1); +} diff --git a/usr/src/lib/libpkg/common/runcmd.c b/usr/src/lib/libpkg/common/runcmd.c new file mode 100644 index 0000000000..945673737e --- /dev/null +++ b/usr/src/lib/libpkg/common/runcmd.c @@ -0,0 +1,808 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <strings.h> +#include <signal.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <wait.h> +#include <sys/types.h> +#include "pkglib.h" +#include "pkglocale.h" +#include "pkglibmsgs.h" + +#ifndef _STDARG_H +#include "stdarg.h" +#endif + +/* + * Private definitions + */ + +/* Maximum number of arguments to pkg_ExecCmdList */ + +#define MAX_EXEC_CMD_ARGS 100 + +/* Size of buffer increments when reading from pipe */ + +#define PIPE_BUFFER_INCREMENT 256 + +static char errfile[L_tmpnam+1]; + +/* + * This is the "argument array" definition that is returned by e_new_args and is + * used by e_add_args, e_free_args, etc. + */ + +struct _argArray_t { + long _aaNumArgs; /* number of arguments set */ + long _aaMaxArgs; /* number of arguments allocated */ + char **_aaArgs; /* actual arguments */ +}; + +typedef struct _argArray_t argArray_t; + +/* + * Private Methods + */ +static void e_free_args(argArray_t *a_args); +static argArray_t *e_new_args(int initialCount); +/*PRINTFLIKE2*/ +static boolean_t e_add_arg(argArray_t *a_args, char *a_format, ...); +static int e_get_argc(argArray_t *a_args); +static char **e_get_argv(argArray_t *a_args); + + +/* + * Public Methods + */ + + +void +rpterr(void) +{ + FILE *fp; + int c; + + if (errfile[0]) { + if (fp = fopen(errfile, "r")) { + while ((c = getc(fp)) != EOF) + putc(c, stderr); + (void) fclose(fp); + } + (void) unlink(errfile); + errfile[0] = '\0'; + } +} + +void +ecleanup(void) +{ + if (errfile[0]) { + (void) unlink(errfile); + errfile[0] = NULL; + } +} + +int +esystem(char *cmd, int ifd, int ofd) +{ + char *perrfile; + int status = 0; + pid_t pid; + + perrfile = tmpnam(NULL); + if (perrfile == NULL) { + progerr( + pkg_gt("unable to create temp error file, errno=%d"), + errno); + return (-1); + } + (void) strlcpy(errfile, perrfile, sizeof (errfile)); + + /* flush standard i/o before creating new process */ + + (void) fflush(stderr); + (void) fflush(stdout); + + /* + * create new process to execute command in; + * vfork() is being used to avoid duplicating the parents + * memory space - this means that the child process may + * not modify any of the parents memory including the + * standard i/o descriptors - all the child can do is + * adjust interrupts and open files as a prelude to a + * call to exec(). + */ + + pid = vfork(); + if (pid == 0) { + /* + * this is the child process + */ + int i; + + /* reset any signals to default */ + + for (i = 0; i < NSIG; i++) { + (void) sigset(i, SIG_DFL); + } + + if (ifd > 0) { + (void) dup2(ifd, STDIN_FILENO); + } + + if (ofd >= 0 && ofd != STDOUT_FILENO) { + (void) dup2(ofd, STDOUT_FILENO); + } + + i = open(errfile, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (i >= 0) { + dup2(i, STDERR_FILENO); + } + + /* Close all open files except standard i/o */ + + closefrom(3); + + /* execute target executable */ + + execl("/sbin/sh", "/sbin/sh", "-c", cmd, NULL); + progerr(pkg_gt("exec of <%s> failed, errno=%d"), cmd, errno); + _exit(99); + } else if (pid < 0) { + /* fork failed! */ + + logerr(pkg_gt("bad vfork(), errno=%d"), errno); + return (-1); + } + + /* + * this is the parent process + */ + + sighold(SIGINT); + pid = waitpid(pid, &status, 0); + sigrelse(SIGINT); + + if (pid < 0) { + return (-1); /* probably interrupted */ + } + + switch (status & 0177) { + case 0: + case 0177: + status = status >> 8; + /*FALLTHROUGH*/ + + default: + /* terminated by a signal */ + status = status & 0177; + } + + if (status == 0) { + ecleanup(); + } + + return (status); +} + +FILE * +epopen(char *cmd, char *mode) +{ + char *buffer, *perrfile; + FILE *pp; + size_t len; + size_t alen; + + if (errfile[0]) { + /* cleanup previous errfile */ + unlink(errfile); + } + + perrfile = tmpnam(NULL); + if (perrfile == NULL) { + progerr( + pkg_gt("unable to create temp error file, errno=%d"), + errno); + return ((FILE *)0); + } + + if (strlcpy(errfile, perrfile, sizeof (errfile)) > sizeof (errfile)) { + progerr(pkg_gt("file name max length %d; name is too long: %s"), + sizeof (errfile), perrfile); + return ((FILE *)0); + } + + len = strlen(cmd)+6+strlen(errfile); + buffer = (char *)calloc(len, sizeof (char)); + if (buffer == NULL) { + progerr(pkg_gt("no memory in epopen(), errno=%d"), errno); + return ((FILE *)0); + } + + if (strchr(cmd, '|')) { + alen = snprintf(buffer, len, "(%s) 2>%s", cmd, errfile); + } else { + alen = snprintf(buffer, len, "%s 2>%s", cmd, errfile); + } + + if (alen > len) { + progerr(pkg_gt("command max length %d; cmd is too long: %s"), + len, cmd); + return ((FILE *)0); + } + + pp = popen(buffer, mode); + + free(buffer); + return (pp); +} + +int +epclose(FILE *pp) +{ + int n; + + n = pclose(pp); + if (n == 0) + ecleanup(); + return (n); +} + +/* + * Name: e_ExecCmdArray + * Synopsis: Execute Unix command and return results + * Description: Execute a Unix command and return results and status + * Arguments: + * r_status - [RO, *RW] - (int *) + * Return (exit) status from Unix command: + * == -1 : child terminated with a signal + * != -1 : lower 8-bit value child passed to exit() + * r_results - [RO, *RW] - (char **) + * Any output generated by the Unix command to stdout + * and to stderr + * == (char *)NULL if no output generated + * a_inputFile - [RO, *RO] - (char *) + * Pointer to character string representing file to be + * used as "standard input" for the command. + * == (char *)NULL to use "/dev/null" as standard input + * a_cmd - [RO, *RO] - (char *) + * Pointer to character string representing the full path + * of the Unix command to execute + * char **a_args - [RO, *RO] - (char **) + * List of character strings representing the arguments + * to be passed to the Unix command. The list must be + * terminated with an element that is (char *)NULL + * Returns: int + * == 0 - Command executed + * Look at r_status for results of Unix command + * != 0 - problems executing command + * r_status and r_results have no meaning; + * r_status will be -1 + * r_results will be NULL + * NOTE: Any results returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the results are no longer needed. + * NOTE: If 0 is returned, 'r_status' must be queried to + * determine the results of the Unix command. + * NOTE: The system "errno" value from immediately after waitpid() call + * is preserved for the calling method to use to determine + * the system reason why the operation failed. + */ + +int +e_ExecCmdArray(int *r_status, char **r_results, + char *a_inputFile, char *a_cmd, char **a_args) +{ + char *buffer; + int bufferIndex; + int bufferSize; + int ipipe[2] = {0, 0}; + pid_t pid; + pid_t resultPid; + int status; + int lerrno; + int stdinfile = -1; + + /* reset return results buffer pointer */ + + if (r_results != (char **)NULL) { + *r_results = (char *)NULL; + } + + *r_status = -1; + + /* + * See if command exists + */ + + if (access(a_cmd, F_OK|X_OK) != 0) { + return (-1); + } + + /* + * See if input file exists + */ + + if (a_inputFile != (char *)NULL) { + stdinfile = open(a_inputFile, O_RDONLY); + } else { + stdinfile = open("/dev/null", O_RDONLY); /* stdin = /dev/null */ + } + + if (stdinfile < 0) { + return (-1); + } + + /* + * Create a pipe to be used to capture the command output + */ + + if (pipe(ipipe) != 0) { + (void) close(stdinfile); + return (-1); + } + + + bufferSize = PIPE_BUFFER_INCREMENT; + bufferIndex = 0; + buffer = calloc(1, bufferSize); + if (buffer == (char *)NULL) { + (void) close(stdinfile); + return (-1); + } + + /* flush standard i/o before creating new process */ + + (void) fflush(stderr); + (void) fflush(stdout); + + /* + * create new process to execute command in; + * vfork() is being used to avoid duplicating the parents + * memory space - this means that the child process may + * not modify any of the parents memory including the + * standard i/o descriptors - all the child can do is + * adjust interrupts and open files as a prelude to a + * call to exec(). + */ + + pid = vfork(); + + if (pid == 0) { + /* + * This is the forked (child) process ====================== + */ + + int i; + + /* reset any signals to default */ + + for (i = 0; i < NSIG; i++) { + (void) sigset(i, SIG_DFL); + } + + /* assign stdin, stdout, stderr as appropriate */ + + (void) dup2(stdinfile, STDIN_FILENO); + (void) close(ipipe[0]); /* close out pipe reader side */ + (void) dup2(ipipe[1], STDOUT_FILENO); + (void) dup2(ipipe[1], STDERR_FILENO); + + /* Close all open files except standard i/o */ + + closefrom(3); + + /* execute target executable */ + + (void) execvp(a_cmd, a_args); + perror(a_cmd); /* Emit error msg - ends up in callers buffer */ + _exit(0x00FE); + } + + /* + * This is the forking (parent) process ==================== + */ + + (void) close(stdinfile); + (void) close(ipipe[1]); /* Close write side of pipe */ + + /* + * Spin reading data from the child into the buffer - when the read eofs + * the child has exited + */ + + for (;;) { + ssize_t bytesRead; + + /* read as much child data as there is available buffer space */ + + bytesRead = read(ipipe[0], buffer + bufferIndex, + bufferSize - bufferIndex); + + /* break out of read loop if end-of-file encountered */ + + if (bytesRead == 0) { + break; + } + + /* if error, continue if recoverable, else break out of loop */ + + if (bytesRead == -1) { + /* try again: EAGAIN - insufficient resources */ + + if (errno == EAGAIN) { + continue; + } + + /* try again: EINTR - interrupted system call */ + + if (errno == EINTR) { + continue; + } + + /* break out of loop - error not recoverable */ + break; + } + + /* at least 1 byte read: expand buffer if at end */ + + bufferIndex += bytesRead; + if (bufferIndex >= bufferSize) { + buffer = realloc(buffer, + bufferSize += PIPE_BUFFER_INCREMENT); + (void) memset(buffer + bufferIndex, 0, + bufferSize - bufferIndex); + } + } + + (void) close(ipipe[0]); /* Close read side of pipe */ + + /* Get subprocess exit status */ + + for (;;) { + resultPid = waitpid(pid, &status, 0L); + lerrno = (resultPid == -1 ? errno : 0); + + /* break loop if child process status reaped */ + + if (resultPid != -1) { + break; + } + + /* break loop if not interrupted out of waitpid */ + + if (errno != EINTR) { + break; + } + } + + /* + * If the child process terminated due to a call to exit(), then + * set results equal to the 8-bit exit status of the child process; + * otherwise, set the exit status to "-1" indicating that the child + * exited via a signal. + */ + + *r_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + + /* return appropriate output */ + + if (!*buffer) { + /* No contents in output buffer - discard */ + free(buffer); + } else if (r_results == (char **)NULL) { + /* Not requested to return results - discard */ + free(buffer); + } else { + /* have output and request to return: pass to calling method */ + *r_results = buffer; + } + + errno = lerrno; + return (resultPid == -1 ? -1 : 0); +} + +/* + * Name: e_ExecCmdList + * Synopsis: Execute Unix command and return results + * Description: Execute a Unix command and return results and status + * Arguments: + * r_status - [RO, *RW] - (int *) + * Return (exit) status from Unix command + * r_results - [RO, *RW] - (char **) + * Any output generated by the Unix command to stdout + * and to stderr + * == (char *)NULL if no output generated + * a_inputFile - [RO, *RO] - (char *) + * Pointer to character string representing file to be + * used as "standard input" for the command. + * == (char *)NULL to use "/dev/null" as standard input + * a_cmd - [RO, *RO] - (char *) + * Pointer to character string representing the full path + * of the Unix command to execute + * ... - [RO] (?) + * Zero or more arguments to the Unix command + * The argument list must be ended with (void *)NULL + * Returns: int + * == 0 - Command executed + * Look at r_status for results of Unix command + * != 0 - problems executing command + * r_status and r_results have no meaning + * NOTE: Any results returned is placed in new storage for the + * calling method. The caller must use 'free' to dispose + * of the storage once the results are no longer needed. + * NOTE: If LU_SUCCESS is returned, 'r_status' must be queried to + * determine the results of the Unix command. + */ + +int +e_ExecCmdList(int *r_status, char **r_results, + char *a_inputFile, char *a_cmd, ...) +{ + va_list ap; /* references variable argument list */ + char *array[MAX_EXEC_CMD_ARGS+1]; + int argno = 0; + + /* + * Create argument array for exec system call + */ + + bzero(array, sizeof (array)); + + va_start(ap, a_cmd); /* Begin variable argument processing */ + + for (argno = 0; argno < MAX_EXEC_CMD_ARGS; argno++) { + array[argno] = va_arg(ap, char *); + if (array[argno] == (char *)NULL) { + break; + } + } + + va_end(ap); + return (e_ExecCmdArray(r_status, r_results, a_inputFile, + a_cmd, array)); +} + +/* + * Name: e_new_args + * Description: create a new argument array for use in exec() calls + * Arguments: initialCount - [RO, *RO] - (int) + * Initial number of elements to populate the + * argument array with - use best guess + * Returns: argArray_t * + * Pointer to argument array that can be used in other + * functions that accept it as an argument + * == (argArray_t *)NULL - error + * NOTE: you must call e_free_args() when the returned argument array is + * no longer needed so that all storage used can be freed up. + */ + +argArray_t * +e_new_args(int initialCount) +{ + argArray_t *aa; + + /* allocate new argument array structure */ + + aa = (argArray_t *)calloc(1, sizeof (argArray_t)); + if (aa == (argArray_t *)NULL) { + progerr(ERR_MALLOC, strerror(errno), sizeof (argArray_t), + "<argArray_t>"); + return ((argArray_t *)NULL); + } + + /* allocate initial argument array */ + + aa->_aaArgs = (char **)calloc(initialCount+1, sizeof (char *)); + if (aa->_aaArgs == (char **)NULL) { + progerr(ERR_MALLOC, strerror(errno), + (initialCount+1)*sizeof (char *), "<char **>"); + return ((argArray_t *)NULL); + } + + /* initialize argument indexes */ + + aa->_aaNumArgs = 0; + aa->_aaMaxArgs = initialCount; + + return (aa); +} + +/* + * Name: e_add_arg + * Description: add new argument to argument array for use in exec() calls + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to add the argument to + * a_format - [RO, *RO] - (char *) + * Pointer to "printf" style format argument + * ... - [RO, *RO] - (varies) + * Arguments as appropriate for format statement + * Returns: boolean_t + * B_TRUE - success + * B_FALSE - failure + * Examples: + * - to add an argument that specifies a file descriptor: + * int fd; + * e_add_arg(aa, "/proc/self/fd/%d", fd); + * - to add a flag or other known text: + * e_add_arg(aa, "-s") + * - to add random text: + * char *random_text; + * e_add_arg(aa, "%s", random_text); + */ + +/*PRINTFLIKE2*/ +boolean_t +e_add_arg(argArray_t *a_args, char *a_format, ...) +{ + char *rstr = (char *)NULL; + char bfr[MAX_CANON]; + size_t vres = 0; + va_list ap; + + /* + * double argument array if array is full + */ + + if (a_args->_aaNumArgs >= a_args->_aaMaxArgs) { + int newMax; + char **newArgs; + + newMax = a_args->_aaMaxArgs * 2; + newArgs = (char **)realloc(a_args->_aaArgs, + (newMax+1) * sizeof (char *)); + if (newArgs == (char **)NULL) { + progerr(ERR_MALLOC, strerror(errno), + ((newMax+1) * sizeof (char *)), "<char **>"); + return (B_FALSE); + } + a_args->_aaArgs = newArgs; + a_args->_aaMaxArgs = newMax; + } + + /* determine size of argument to add to list */ + + va_start(ap, a_format); + vres = vsnprintf(bfr, sizeof (bfr), a_format, ap); + va_end(ap); + + /* if it fit in the built in buffer, use that */ + if (vres < sizeof (bfr)) { + /* dup text already generated in bfr */ + rstr = strdup(bfr); + if (rstr == (char *)NULL) { + progerr(ERR_MALLOC, strerror(errno), vres+2, + "<char *>"); + return (B_FALSE); + } + } else { + /* allocate space for argument to add */ + + rstr = (char *)malloc(vres+2); + if (rstr == (char *)NULL) { + progerr(ERR_MALLOC, strerror(errno), vres+2, + "<char *>"); + return (B_FALSE); + } + + /* generate argument to add */ + + va_start(ap, a_format); + vres = vsnprintf(rstr, vres+1, a_format, ap); + va_end(ap); + } + + /* add argument to the end of the argument array */ + + a_args->_aaArgs[a_args->_aaNumArgs++] = rstr; + a_args->_aaArgs[a_args->_aaNumArgs] = (char *)NULL; + + return (B_TRUE); +} + +/* + * Name: e_get_argv + * Description: return (char **)argv pointer from argument array + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to return argv pointer for + * Returns: char ** + * Pointer to (char **)argv pointer suitable for use + * in an exec*() call + * NOTE: the actual character array is always terminated with a (char *)NULL + */ + +char ** +e_get_argv(argArray_t *a_args) +{ + return (a_args->_aaArgs); +} + +/* + * Name: e_get_argc + * Description: return (int) argc count from argument array + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to return argc count for + * Returns: int + * Count of the number of arguments in the argument array + * suitable for use in an exec*() call + */ + +int +e_get_argc(argArray_t *a_args) +{ + return (a_args->_aaNumArgs); +} + +/* + * Name: e_free_args + * Description: free all storage contained in an argument array previously + * allocated by a call to e_new_args + * Arguments: a_args - [RO, *RW] - (argArray_t *) + * Pointer to argument array (previously allocated via + * a call to e_new_args) to free + * Returns: void + * NOTE: preserves errno (usually called right after e_execCmd*()) + */ + +void +e_free_args(argArray_t *a_args) +{ + int i; + int lerrno = errno; + + /* free all arguments in the argument array */ + + for (i = (a_args->_aaNumArgs-1); i >= 0; i--) { + (void) free(a_args->_aaArgs[i]); + a_args->_aaArgs[i] = (char *)NULL; + } + + /* free argument array */ + + (void) free(a_args->_aaArgs); + + /* free argument array structure */ + + (void) free(a_args); + + /* restore errno */ + + errno = lerrno; +} diff --git a/usr/src/lib/libpkg/common/security.c b/usr/src/lib/libpkg/common/security.c new file mode 100644 index 0000000000..9f2070c0c6 --- /dev/null +++ b/usr/src/lib/libpkg/common/security.c @@ -0,0 +1,282 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +/* + * Module: security.c + * Description: + * Module for handling certificates and various + * utilities to access their data. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <limits.h> +#include <pkgstrct.h> +#include <pkginfo.h> +#include <locale.h> +#include <libintl.h> +#include <unistd.h> +#include <stdlib.h> + +#include <openssl/bio.h> +#include <openssl/pkcs12.h> +#include <openssl/pkcs7.h> +#include <openssl/x509.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include "pkgerr.h" +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" +#include "p12lib.h" + +/* length of allowable passwords */ +#define MAX_PASSLEN 128 + +/* + * Name: init_security + * Description: Initializes structures, libraries, for security operations + * Arguments: none + * Returns: 0 if we couldn't initialize, non-zero otherwise + */ +void +sec_init(void) +{ + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + ERR_load_SUNW_strings(); + (void) SSL_library_init(); +} + +/* + * get_cert_chain - Builds a chain of certificates, from a given + * user certificate to a trusted certificate. + * + * Arguments: + * err - Error object to add errors to + * cert - User cert to start with + * cas - Trusted certs to use as trust anchors + * chain - The resulting chain of certs (in the form of an + * ordered set) is placed here. + * + * Returns: + * 0 - Success - chain is stored in 'chain'. + * non-zero - Failure, errors recorded in err + */ +int +get_cert_chain(PKG_ERR *err, X509 *cert, STACK_OF(X509) *clcerts, + STACK_OF(X509) *cas, STACK_OF(X509) **chain) +{ + X509_STORE_CTX *store_ctx = NULL; + X509_STORE *ca_store = NULL; + X509 *ca_cert = NULL; + int i; + int ret = 0; + + if ((ca_store = X509_STORE_new()) == NULL) { + pkgerr_add(err, PKGERR_NOMEM, + gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + + /* add all ca certs into the store */ + for (i = 0; i < sk_X509_num(cas); i++) { + /* LINTED pointer cast may result in improper alignment */ + ca_cert = sk_X509_value(cas, i); + if (X509_STORE_add_cert(ca_store, ca_cert) == 0) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + } + + /* initialize context object used during the chain resolution */ + + if ((store_ctx = X509_STORE_CTX_new()) == NULL) { + pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM)); + ret = 1; + goto cleanup; + } + + (void) X509_STORE_CTX_init(store_ctx, ca_store, cert, clcerts); + /* attempt to verify the cert, which builds the cert chain */ + if (X509_verify_cert(store_ctx) <= 0) { + pkgerr_add(err, PKGERR_CHAIN, + gettext(ERR_CERTCHAIN), + get_subject_display_name(cert), + X509_verify_cert_error_string(store_ctx->error)); + ret = 1; + goto cleanup; + } + *chain = X509_STORE_CTX_get1_chain(store_ctx); + +cleanup: + if (ca_store != NULL) + (void) X509_STORE_free(ca_store); + if (store_ctx != NULL) { + (void) X509_STORE_CTX_cleanup(store_ctx); + (void) X509_STORE_CTX_free(store_ctx); + } + + return (ret); +} + +/* + * Name: get_subject_name + * Description: Retrieves a name used for identifying a certificate's subject. + * + * Arguments: cert - The certificate to get the name from + * + * Returns : A static buffer containing the common name (CN) of the + * subject of the cert. + * + * if the CN is not available, returns a string with the entire + * X509 distinguished name. + */ +char +*get_subject_display_name(X509 *cert) +{ + + X509_NAME *xname; + static char sname[ATTR_MAX]; + + xname = X509_get_subject_name(cert); + if (X509_NAME_get_text_by_NID(xname, + NID_commonName, sname, + ATTR_MAX) <= 0) { + (void) strncpy(sname, + X509_NAME_oneline(xname, + NULL, 0), ATTR_MAX); + sname[ATTR_MAX - 1] = '\0'; + } + return (sname); +} + +/* + * Name: get_display_name + * Description: Retrieves a name used for identifying a certificate's issuer. + * + * Arguments: cert - The certificate to get the name from + * + * Returns : A static buffer containing the common name (CN) + * of the issuer of the cert. + * + * if the CN is not available, returns a string with the entire + * X509 distinguished name. + */ +char +*get_issuer_display_name(X509 *cert) +{ + + X509_NAME *xname; + static char sname[ATTR_MAX]; + + xname = X509_get_issuer_name(cert); + if (X509_NAME_get_text_by_NID(xname, + NID_commonName, sname, + ATTR_MAX) <= 0) { + (void) strncpy(sname, + X509_NAME_oneline(xname, + NULL, 0), ATTR_MAX); + sname[ATTR_MAX - 1] = '\0'; + } + return (sname); +} + + +/* + * Name: get_serial_num + * Description: Retrieves the serial number of an X509 cert + * + * Arguments: cert - The certificate to get the data from + * + * Returns : A static buffer containing the serial number + * of the cert + * + * if the SN is not available, returns NULL + */ +char +*get_serial_num(X509 *cert) +{ + static char sn_str[ATTR_MAX]; + ASN1_INTEGER *sn; + + if ((sn = X509_get_serialNumber(cert)) != 0) { + return (NULL); + } else { + (void) snprintf(sn_str, ATTR_MAX, "%ld", + ASN1_INTEGER_get(sn)); + } + + return (sn_str); +} + +/* + * Name: get_fingerprint + * Description: Generates a fingerprint string given + * a digest algorithm with which to calculate + * the fingerprint + * + * Arguments: cert - The certificate to get the data from + * Arguments: alg - The algorithm to use to calculate the fingerprint + * + * Returns : A static buffer containing the digest + * NULL if cert is NULL, or digest cannot be calculated + */ +char +*get_fingerprint(X509 *cert, const EVP_MD *alg) +{ + static char fp_str[ATTR_MAX]; + char tmp[ATTR_MAX] = ""; + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + int i; + + if (!X509_digest(cert, alg, md, &n)) { + return (NULL); + } + + /* start with empty string */ + fp_str[0] = '\0'; + + for (i = 0; i < (int)n; i++) { + /* form a byte of the fingerprint */ + (void) snprintf(tmp, ATTR_MAX, "%02X:", md[i]); + /* cat it onto the end of the result */ + (void) strlcat(fp_str, tmp, ATTR_MAX); + } + + /* nuke trailing ':' */ + fp_str[strlen(fp_str) - 1] = '\0'; + + return (fp_str); +} diff --git a/usr/src/lib/libpkg/common/srchcfile.c b/usr/src/lib/libpkg/common/srchcfile.c new file mode 100644 index 0000000000..dd24a855cd --- /dev/null +++ b/usr/src/lib/libpkg/common/srchcfile.c @@ -0,0 +1,1278 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <ctype.h> +#include <sys/types.h> +#include <libintl.h> +#include "pkglib.h" +#include "pkgstrct.h" +#include "pkglocale.h" +#include "pkglibmsgs.h" + +/* + * Forward declarations + */ + +static void findend(char **cp); +static int getend(char **cp); +static int getstr(char **cp, int n, char *str, int separator[]); + +/* from gpkgmap.c */ +int getnumvfp(char **cp, int base, long *d, long bad); +int getlnumvfp(char **cp, int base, fsblkcnt_t *d, long bad); + +/* + * Module globals + */ + +static char lpath[PATH_MAX]; /* for ept->path */ +static char mylocal[PATH_MAX]; /* for ept->ainfo.local */ +static int decisionTableInit = 0; + +/* + * These arrays must be indexable by an unsigned char. + */ + +static int ISPKGPATHSEP[UCHAR_MAX+1]; +static int ISWORDSEP[UCHAR_MAX+1]; +static int ISPKGNAMESEP[UCHAR_MAX+1]; + +/* + * Name: WRITEDATA + * Description: write out data to VFP_T given start and end pointers + * Arguments: VFP - (VFP_T *) - [RO, *RW] + * Contents file VFP to narrow search on + * FIRSTPOS - (char *) - [RO, *RO] + * Pointer to first byte to write out + * LASTPOS - (char *) - [RO, *RO] + * Pointer to last byte to write out + */ + +#define WRITEDATA(VFP, FIRSTPOS, LASTPOS) \ + { \ + ssize_t XXlenXX; \ + /* compute number of bytes skipped */ \ + XXlenXX = (ptrdiff_t)(LASTPOS) - (ptrdiff_t)(FIRSTPOS); \ + /* write the bytes out */ \ + vfpPutBytes((VFP), (FIRSTPOS), XXlenXX); \ + } + +/* + * Name: COPYPATH + * Description: copy path limiting size to destination capacity + * Arguments: DEST - (char []) - [RW] + * SRC - (char *) - [RO, *RO] + * Pointer to first byte of path to copy + * LEN - (int) - [RO] + * Number of bytes to copy + */ + +#define COPYPATH(DEST, SRC, LEN) \ + { \ + /* assure return path does not overflow */ \ + if ((LEN) > sizeof ((DEST))) { \ + (LEN) = sizeof ((DEST))-1; \ + } \ + /* copy return path to local storage */ \ + (void) memcpy((DEST), (SRC), (LEN)); \ + (DEST)[(LEN)] = '\0'; \ + } + +/* + * Name: narrowSearch + * Description: narrow the search location for a specified path + * The contents and package map files are always sorted by path. + * This function is given a target path to search for given the + * current location in a contents file. It is assured that the + * target path has not been searched for yet in the contents file + * so the current location in the contents file is guaranteed to + * be less than the location of the target path (if present). + * Given this employ a binary search to speed up the search for + * the path nearest to a specified target path. + * Arguments: a_vfp - (VFP_T *) - [RO, *RW] + * Contents file VFP to narrow search on + * a_path - (char *) - [RO, *RO] + * Pointer to path to search for + * a_pathLen - (size_t) - [RO] + * Length of string (a_path) + * Returns: char * - pointer to first byte of entry in contents file that + * is guaranteed to be the closest match to the specified + * a_path without being "greater than" the path. + * == (char *)NULL if no entry found + */ + +static char * +narrowSearch(VFP_T *a_vfp, char *a_path, size_t a_pathLen) +{ + char *phigh; + char *plow; + char *pmid; + int n; + size_t plen; + + /* if no path to compare, start at beginning */ + + if ((a_path == (char *)NULL) || (*a_path == '\0')) { + return ((char *)NULL); + } + + /* if the contents file is empty, resort to sequential search */ + + if (vfpGetBytesRemaining(a_vfp) <= 1) { + return ((char *)NULL); + } + + /* + * test against first path - if the path specified is less than the + * first path in the contents file, then the path can be inserted + * before the first entry in the contents file. + */ + + /* locate start of first line */ + + plow = vfpGetCurrCharPtr(a_vfp); + pmid = plow; + + /* if first path not absolute, resort to sequential search */ + + if (*pmid != '/') { + return ((char *)NULL); + } + + /* find end of path */ + + while (ISPKGPATHSEP[(int)*pmid] == 0) { + pmid++; + } + + /* determine length of path */ + + plen = (ptrdiff_t)pmid - (ptrdiff_t)plow; + + /* compare target path with current path */ + + n = strncmp(a_path, plow, plen); + if (n == 0) { + /* if lengths same exact match return position found */ + if (a_pathLen == plen) { + return (plow); + } + /* not exact match - a_path > pm */ + n = a_pathLen; + } + + /* return if target is less than or equal to first entry */ + + if (n <= 0) { + return (plow); + } + + /* + * test against last path - if the path specified is greater than the + * last path in the contents file, then the path can be appended after + * the last entry in the contents file. + */ + + /* locate start of last line */ + + plow = vfpGetCurrCharPtr(a_vfp); + pmid = vfpGetLastCharPtr(a_vfp); + + while ((pmid > plow) && (!((pmid[0] == '/') && (pmid[-1] == '\n')))) { + pmid--; + } + + /* if absolute path, do comparison */ + + if ((pmid > plow) && (*pmid == '/')) { + plow = pmid; + + /* find end of path */ + + while (ISPKGPATHSEP[(int)*pmid] == 0) { + pmid++; + } + + /* determine length of path */ + + plen = (ptrdiff_t)pmid - (ptrdiff_t)plow; + + /* compare target path with current path */ + + n = strncmp(a_path, plow, plen); + if (n == 0) { + /* if lengths same exact match return position found */ + if (a_pathLen == plen) { + return (plow); + } + /* not exact match - a_path > pm */ + n = a_pathLen; + } + + /* return if target is greater than or equal to entry */ + + if (n >= 0) { + return (plow); + } + } + /* + * firstPath < targetpath < lastPath: + * binary search looking for closest "less than" match + */ + + plow = vfpGetCurrCharPtr(a_vfp); + phigh = vfpGetLastCharPtr(a_vfp); + + for (;;) { + char *pm; + + /* determine number of bytes left in search area */ + + plen = (ptrdiff_t)phigh - (ptrdiff_t)plow; + + /* calculate mid point between current low and high points */ + + pmid = plow + (plen >> 1); + + /* backup and find first "\n/" -or- start of buffer */ + + while ((pmid > plow) && + (!((pmid[0] == '/') && (pmid[-1] == '\n')))) { + pmid--; + } + + /* return lowest line found if current line not past that */ + + if (pmid <= plow) { + return (plow); + } + + /* remember start of this line */ + + pm = pmid; + + /* find end of path */ + + while (ISPKGPATHSEP[(int)*pmid] == 0) { + pmid++; + } + + /* determine length of path */ + + plen = (ptrdiff_t)pmid - (ptrdiff_t)pm; + + /* compare target path with current path */ + + n = strncmp(a_path, pm, plen); + + if (n == 0) { + /* if lengths same exact match return position found */ + if (a_pathLen == plen) { + return (pm); + } + /* not exact match - a_path > pm */ + n = a_pathLen; + } + + + /* not exact match - determine which watermark to split */ + + if (n > 0) { /* a_path > pm */ + plow = pm; + } else { /* a_path < pm */ + phigh = pm; + } + } + /*NOTREACHED*/ +} + +/* + * Name: srchcfile + * Description: search contents file looking for closest match to entry, + * creating a new contents file if output contents file specified + * Arguments: ept - (struct cfent *) - [RO, *RW] + * - contents file entry, describing last item found + * path - (char *) - [RO, *RO] + * - path to search for in contents file + * - If path is "*", then the next entry is returned; + * the next entry always matches this path + * - If the path is (char *)NULL or "", then all remaining + * entries are processed and copied out to the + * file specified by cfTmpVFp + * cfVfp - (VFP_T *) - [RO, *RW] + * - VFP_T open on contents file to search + * cfTmpVfp - (VFP_T *) - [RO, *RW] + * - VFP_T open on temporary contents file to populate + * Returns: int + * < 0 - error occurred + * - Use getErrstr to retrieve character-string describing + * the reason for failure + * == 0 - no match found + * - specified path not in the contents file + * - all contents of cfVfp copied to cfTmpVfp + * - current character of cfVfp is at end of file + * == 1 - exact match found + * - specified path found in contents file + * - contents of cfVfp up to entry found copied to cfTmpVfp + * - current character of cfVfp is first character of + * entry found + * - this value is always returned if path is "*" and the + * next entry is returned - -1 is returned when no more + * entries are left to process + * == 2 - entry found which is GREATER than path specified + * - specified path would fit BEFORE entry found + * - contents of cfVfp up to entry found copied to cfTmpVfp + * - current character of cfVfp is first character of + * entry found + * Side Effects: + * - The ept structure supplied is filled in with a description of + * the item that caused the search to terminate, except in the + * case of '0' in which case the contents of 'ept' is undefined. + * - NOTE: the ept->path item points to a path that is statically + * allocated and will be overwritten on the next call. + * - NOTE: the ept->ainfo.local item points to a path that is + * statically allocated and will be overwritten on the next call. + */ + +int +srchcfile(struct cfent *ept, char *path, VFP_T *cfVfp, VFP_T *cfTmpVfp) +{ + char *cpath_start = (char *)NULL; + char *firstPos = vfpGetCurrCharPtr(cfVfp); + char *lastPos = NULL; + char *pos; + char classname[CLSSIZ+1]; + char pkgname[PKGSIZ+1]; + int anypath = 0; + int c; + int dataSkipped = 0; + int n; + int rdpath; + size_t cpath_len = 0; + size_t pathLength; + struct pinfo *lastpinfo; + struct pinfo *pinfo; + + /* + * this code does not use nested subroutines because execution time + * of this routine is especially critical to installation and upgrade + */ + + /* initialize local variables */ + + setErrstr(NULL); /* no error message currently cached */ + pathLength = (path == (char *)NULL ? 0 : strlen(path)); + lpath[0] = '\0'; + lpath[sizeof (lpath)-1] = '\0'; + + /* initialize ept structure values */ + + (void) strlcpy(ept->ainfo.group, BADGROUP, sizeof (ept->ainfo.group)); + (void) strlcpy(ept->ainfo.owner, BADOWNER, sizeof (ept->ainfo.owner)); + (void) strlcpy(ept->pkg_class, BADCLASS, sizeof (ept->pkg_class)); + ept->ainfo.local = (char *)NULL; + ept->ainfo.mode = BADMODE; + ept->cinfo.cksum = BADCONT; + ept->cinfo.modtime = BADCONT; + ept->cinfo.size = (fsblkcnt_t)BADCONT; + ept->ftype = BADFTYPE; + ept->npkgs = 0; + ept->path = (char *)NULL; + ept->pinfo = (struct pinfo *)NULL; + ept->pkg_class_idx = -1; + ept->volno = 0; + + /* + * populate decision tables that implement fast character checking; + * this is much faster than the equivalent strpbrk() call or a + * while() loop checking for the characters. It is only faster if + * there are at least 3 characters to scan for - when checking for + * one or two characters (such as '\n' or '\0') its faster to do + * a simple while() loop. + */ + + if (decisionTableInit == 0) { + /* + * any chars listed stop scan; + * scan stops on first byte found that is set to '1' below + */ + + /* + * Separators for path names, normal space and = + * for linked filenames + */ + bzero(ISPKGPATHSEP, sizeof (ISPKGPATHSEP)); + ISPKGPATHSEP['='] = 1; /* = */ + ISPKGPATHSEP[' '] = 1; /* space */ + ISPKGPATHSEP['\t'] = 1; /* horizontal-tab */ + ISPKGPATHSEP['\n'] = 1; /* new-line */ + ISPKGPATHSEP['\0'] = 1; /* NULL character */ + + /* + * Separators for normal words + */ + bzero(ISWORDSEP, sizeof (ISWORDSEP)); + ISWORDSEP[' '] = 1; + ISWORDSEP['\t'] = 1; + ISWORDSEP['\n'] = 1; + ISWORDSEP['\0'] = 1; + + /* + * Separators for list of packages, includes \\ for + * alternate ftype and : for classname + */ + bzero(ISPKGNAMESEP, sizeof (ISPKGNAMESEP)); + ISPKGNAMESEP[' '] = 1; + ISPKGNAMESEP['\t'] = 1; + ISPKGNAMESEP['\n'] = 1; + ISPKGNAMESEP[':'] = 1; + ISPKGNAMESEP['\\'] = 1; + ISPKGNAMESEP['\0'] = 1; + + decisionTableInit = 1; + } + + /* if no bytes in contents file, return 0 */ + + if (vfpGetBytesRemaining(cfVfp) <= 1) { + return (0); + } + + /* if the path to scan for is empty, act like no path was specified */ + + if ((path != (char *)NULL) && (*path == '\0')) { + path = (char *)NULL; + } + + /* + * if path to search for is "*", then we will return the first path + * we encounter as a match, otherwise we return an error + */ + + if ((path != (char *)NULL) && (path[0] != '/')) { + if (strcmp(path, "*") != 0) { + setErrstr(pkg_gt(ERR_ILLEGAL_SEARCH_PATH)); + return (-1); + } + anypath = 1; + } + + /* attempt to narrow down the search for the specified path */ + + if (anypath == 0) { + char *np; + + np = narrowSearch(cfVfp, path, pathLength); + if (np != (char *)NULL) { + dataSkipped = 1; + lastPos = np; + vfpSetCurrCharPtr(cfVfp, np); + } + } + + /* + * If the path to search for in the source contents file is NULL, then + * this is a request to scan to the end of the source contents file. If + * there is a temporary contents file to copy entries to, all that needs + * to be done is to copy the data remaining from the current location in + * the source contents file to the end of the temporary contents file. + * if there is no temporary contents file to copy to, then all that + * needs to be done is to seek to the end of the source contents file. + */ + + if ((anypath == 0) && (path == (char *)NULL)) { + if (cfTmpVfp != (VFP_T *)NULL) { + if (vfpGetBytesRemaining(cfVfp) > 0) { + WRITEDATA(cfTmpVfp, firstPos, + vfpGetLastCharPtr(cfVfp)+1); + } + *vfpGetLastCharPtr(cfTmpVfp) = '\0'; + } + vfpSeekToEnd(cfVfp); + return (0); + } + + /* + * ********************************************************************* + * main loop processing entries from the contents file looking for + * the specified path + * ********************************************************************* + */ + + for (;;) { + char *p; + + /* not reading old style entry */ + + rdpath = 0; + + /* determine first character of the next entry */ + + if (vfpGetBytesRemaining(cfVfp) <= 0) { + /* no bytes in contents file current char is NULL */ + + c = '\0'; + } else { + /* grab path from first entry */ + + c = vfpGetcNoInc(cfVfp); + } + + /* save current position in file */ + + pos = vfpGetCurrCharPtr(cfVfp); + + /* + * ============================================================= + * at the first character of the next entry in the contents file + * if not absolute path check for exceptions and old style entry + * --> if end of contents file write out skipped data and return + * --> if comment character skip to end of line and restart loop + * --> else process "old style entry: ftype class path" + * ============================================================= + */ + + if (c != '/') { + /* if NULL character then end of contents file found */ + + if (c == '\0') { + /* write out skipped data before returning */ + if (dataSkipped && + (cfTmpVfp != (VFP_T *)NULL)) { + WRITEDATA(cfTmpVfp, firstPos, lastPos); + *vfpGetLastCharPtr(cfTmpVfp) = '\0'; + } + + return (0); /* no more entries */ + } + + /* ignore lines that begin with #, : or a "space" */ + + if ((isspace(c) != 0) || (c == '#') || (c == ':')) { + /* line is a comment */ + findend(&vfpGetCurrCharPtr(cfVfp)); + continue; + } + + /* + * old style entry - format is: + * ftype class path + * set ept->ftype to the type + * set ept->class to the class + * set ept->path to point to lpath + * set cpath_start/cpath_len to point to the file name + * set rdpath to '1' to indicate old style entry parsed + */ + + while (isspace((c = vfpGetc(cfVfp)))) + ; + + switch (c) { + case '?': case 'f': case 'v': case 'e': case 'l': + case 's': case 'p': case 'c': case 'b': case 'd': + case 'x': + /* save ftype */ + ept->ftype = (char)c; + + /* save class */ + if (getstr(&vfpGetCurrCharPtr(cfVfp), CLSSIZ, + ept->pkg_class, ISWORDSEP)) { + setErrstr(ERR_CANNOT_READ_CLASS_TOKEN); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + + /* + * locate file name up to "=", set cpath_start + * and cpath_len to point to the file name + */ + cpath_start = vfpGetCurrCharPtr(cfVfp); + p = vfpGetCurrCharPtr(cfVfp); + + /* + * skip past all bytes until first '= \t\n\0': + */ + while (ISPKGPATHSEP[(int)*p] == 0) { + p++; + } + + cpath_len = vfpGetCurrPtrDelta(cfVfp, p); + + /* + * if the path is zero bytes, line is corrupted + */ + + if (cpath_len < 1) { + setErrstr(ERR_CANNOT_READ_PATHNAME_FLD); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + + vfpIncCurrPtrBy(cfVfp, cpath_len); + + /* set path to point to local path cache */ + ept->path = lpath; + + /* set flag indicating path already parsed */ + rdpath = 1; + break; + + case '\0': + /* end of line before new-line seen */ + vfpDecCurrPtr(cfVfp); + setErrstr(ERR_INCOMPLETE_ENTRY); + return (-1); + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* volume number seen */ + setErrstr(ERR_VOLUMENO_UNEXPECTED); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + + case 'i': + /* type i files are not cataloged */ + setErrstr(ERR_FTYPE_I_UNEXPECTED); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + + default: + /* unknown ftype */ + setErrstr(ERR_UNKNOWN_FTYPE); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + } else { + /* + * current entry DOES start with absolute path + * set ept->path to point to lpath + * set cpath_start/cpath_len to point to the file name + */ + /* copy first token into path element of passed structure */ + + cpath_start = vfpGetCurrCharPtr(cfVfp); + + p = cpath_start; + + /* + * skip past all bytes until first from '= \t\n\0': + */ + + while (ISPKGPATHSEP[(int)*p] == 0) { + p++; + } + + cpath_len = vfpGetCurrPtrDelta(cfVfp, p); + + vfpIncCurrPtrBy(cfVfp, cpath_len); + + if (vfpGetcNoInc(cfVfp) == '\0') { + setErrstr(ERR_INCOMPLETE_ENTRY); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + + ept->path = lpath; + } + + /* + * ============================================================= + * if absolute path then the path is collected and we are at the + * first byte following the absolute path name; + * if not an absolute path then an old style entry, ept has been + * filled with the type and class and path name. + * determine if we have read the pathname which identifies + * the entry we are searching for + * ============================================================= + */ + + if (anypath != 0) { + n = 0; /* next entry is "equal to" */ + } else if (path == (char *)NULL) { + n = 1; /* next entry is "greater than" */ + } else { + n = strncmp(path, cpath_start, cpath_len); + if ((n == 0) && (cpath_len != pathLength)) { + n = cpath_len; + } + } + + /* get first character following the end of the path */ + + c = vfpGetc(cfVfp); + + /* + * if an exact match, always parse out the local path + */ + + if (n == 0) { + /* + * we want to return information about this path in + * the structure provided, so parse any local path + * and jump to code which parses rest of the input line + */ + if (c == '=') { + /* parse local path specification */ + if (getstr(&vfpGetCurrCharPtr(cfVfp), PATH_MAX, + mylocal, ISWORDSEP)) { + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_CANNOT_READ_LL_PATH); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + ept->ainfo.local = mylocal; + } + } + + /* + * if an exact match and processing a new style entry, read the + * remaining information from the new style entry - if this is + * an old style entry (rdpath != 0) then the existing info has + * already been processed as it exists before the pathname and + * not after like a new style entry + */ + + if (n == 0 && rdpath == 0) { + while (isspace((c = vfpGetc(cfVfp)))) + ; + + switch (c) { + case '?': case 'f': case 'v': case 'e': case 'l': + case 's': case 'p': case 'c': case 'b': case 'd': + case 'x': + /* save ftype */ + ept->ftype = (char)c; + + /* save class */ + if (getstr(&vfpGetCurrCharPtr(cfVfp), CLSSIZ, + ept->pkg_class, ISWORDSEP)) { + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_CANNOT_READ_CLASS_TOKEN); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + break; /* we already read the pathname */ + + case '\0': + /* end of line before new-line seen */ + vfpDecCurrPtr(cfVfp); + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_INCOMPLETE_ENTRY); + return (-1); + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_VOLUMENO_UNEXPECTED); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + + case 'i': + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_FTYPE_I_UNEXPECTED); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + + default: + /* unknown ftype */ + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_UNKNOWN_FTYPE); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + } + + /* + * if an exact match all processing is completed; break out of + * the main processing loop and finish processing this entry + * prior to returning to the caller. + */ + + if (n == 0) { + break; + } + + /* + * this entry is not an exact match for the path being searched + * for - if this entry is GREATER THAN the path being searched + * for then finish processing and return GREATER THAN result + * to the caller so the entry for the path being searched for + * can be added to the contents file. + */ + + if (n < 0) { + /* + * the entry we want would fit BEFORE the one we just + * read, so we need to unread what we've read by + * seeking back to the start of this entry + */ + + vfpSetCurrCharPtr(cfVfp, pos); + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + /* write out any skipped data before returning */ + if (dataSkipped && (cfTmpVfp != (VFP_T *)NULL)) { + WRITEDATA(cfTmpVfp, firstPos, lastPos); + } + + return (2); /* path would insert here */ + } + + /* + * This entry is "LESS THAN" the specified path to search for + * need to process the next entry from the contents file. First, + * if writing to new contents file, update new contents file if + * processing old style entry; otherwise, update skipped data + * information to remember current last byte of skipped data. + */ + + if (cfTmpVfp != (VFP_T *)NULL) { + char *px; + ssize_t len; + + if (rdpath != 0) { + /* modify record: write out any skipped data */ + if (dataSkipped) { + WRITEDATA(cfTmpVfp, firstPos, lastPos); + } + + /* + * copy what we've read and the rest of this + * line onto the specified output stream + */ + vfpPutBytes(cfTmpVfp, cpath_start, cpath_len); + vfpPutc(cfTmpVfp, c); + vfpPutc(cfTmpVfp, ept->ftype); + vfpPutc(cfTmpVfp, ' '); + vfpPuts(cfTmpVfp, ept->pkg_class); + + px = strchr(vfpGetCurrCharPtr(cfVfp), '\n'); + + if (px == (char *)NULL) { + len = vfpGetBytesRemaining(cfVfp); + vfpPutBytes(cfTmpVfp, + vfpGetCurrCharPtr(cfVfp), len); + vfpPutc(cfTmpVfp, '\n'); + vfpSeekToEnd(cfVfp); + } else { + len = vfpGetCurrPtrDelta(cfVfp, px); + vfpPutBytes(cfTmpVfp, + vfpGetCurrCharPtr(cfVfp), len); + vfpIncCurrPtrBy(cfVfp, len); + } + + /* reset skiped bytes if any data skipped */ + if (dataSkipped) { + dataSkipped = 0; + lastPos = (char *)NULL; + firstPos = vfpGetCurrCharPtr(cfVfp); + } + } else { + /* skip data */ + dataSkipped = 1; + + px = strchr(vfpGetCurrCharPtr(cfVfp), '\n'); + + if (px == (char *)NULL) { + vfpSeekToEnd(cfVfp); + } else { + len = vfpGetCurrPtrDelta(cfVfp, px)+1; + vfpIncCurrPtrBy(cfVfp, len); + } + lastPos = vfpGetCurrCharPtr(cfVfp); + } + } else { + /* + * since this isn't the entry we want, just read the + * stream until we find the end of this entry and + * then start this search loop again + */ + char *px; + + px = strchr(vfpGetCurrCharPtr(cfVfp), '\n'); + + if (px == (char *)NULL) { + vfpSeekToEnd(cfVfp); + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(pkg_gt(ERR_MISSING_NEWLINE)); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } else { + ssize_t len; + + len = vfpGetCurrPtrDelta(cfVfp, px)+1; + vfpIncCurrPtrBy(cfVfp, len); + } + } + } + + /* + * ********************************************************************* + * end of main loop processing entries from contents file + * the loop is broken out of when an exact match for the + * path being searched for has been found and the type is one of: + * - ?fvelspcbdx + * at this point parsing is at the first character past the full path + * name on an exact match for the path being looked for - parse the + * remainder of the entries information into the ept structure. + * ********************************************************************* + */ + + /* link/symbolic link must have link destination */ + + if (((ept->ftype == 's') || (ept->ftype == 'l')) && + (ept->ainfo.local == NULL)) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_NO_LINK_SOURCE_SPECIFIED); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + + /* character/block devices have major/minor device numbers */ + + if (((ept->ftype == 'c') || (ept->ftype == 'b'))) { + ept->ainfo.major = BADMAJOR; + ept->ainfo.minor = BADMINOR; + if (getnumvfp(&vfpGetCurrCharPtr(cfVfp), 10, + (long *)&ept->ainfo.major, BADMAJOR) || + getnumvfp(&vfpGetCurrCharPtr(cfVfp), 10, + (long *)&ept->ainfo.minor, BADMINOR)) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(pkg_gt(ERR_CANNOT_READ_MM_NUMS)); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + } + + /* most types have mode, owner, group identification components */ + + if ((ept->ftype == 'd') || (ept->ftype == 'x') || (ept->ftype == 'c') || + (ept->ftype == 'b') || (ept->ftype == 'p') || + (ept->ftype == 'f') || (ept->ftype == 'v') || + (ept->ftype == 'e')) { + /* mode, owner, group should be here */ + if (getnumvfp(&vfpGetCurrCharPtr(cfVfp), 8, + (long *)&ept->ainfo.mode, BADMODE) || + getstr(&vfpGetCurrCharPtr(cfVfp), sizeof (ept->ainfo.owner), + ept->ainfo.owner, ISWORDSEP) || + getstr(&vfpGetCurrCharPtr(cfVfp), sizeof (ept->ainfo.group), + ept->ainfo.group, ISWORDSEP)) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_CANNOT_READ_MOG); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + } + + /* i/f/v/e have size, checksum, modification time components */ + + if ((ept->ftype == 'i') || (ept->ftype == 'f') || + (ept->ftype == 'v') || (ept->ftype == 'e')) { + /* look for content description */ + if (getlnumvfp(&vfpGetCurrCharPtr(cfVfp), 10, + (fsblkcnt_t *)&ept->cinfo.size, BADCONT) || + getnumvfp(&vfpGetCurrCharPtr(cfVfp), 10, + (long *)&ept->cinfo.cksum, BADCONT) || + getnumvfp(&vfpGetCurrCharPtr(cfVfp), 10, + (long *)&ept->cinfo.modtime, BADCONT)) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_CANNOT_READ_CONTENT_INFO); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + } + + /* i files processing is completed - return 'exact match found' */ + + if (ept->ftype == 'i') { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + if (getend(&vfpGetCurrCharPtr(cfVfp))) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_EXTRA_TOKENS); + return (-1); + } + + /* write out any skipped data before returning */ + if (dataSkipped && (cfTmpVfp != (VFP_T *)NULL)) { + WRITEDATA(cfTmpVfp, firstPos, lastPos); + } + + return (1); + } + + /* + * determine list of packages which reference this entry + */ + + lastpinfo = (struct pinfo *)NULL; + while ((c = getstr(&vfpGetCurrCharPtr(cfVfp), sizeof (pkgname), + pkgname, ISPKGNAMESEP)) <= 0) { + /* if c < 0 the string was too long to fix in the buffer */ + + if (c < 0) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_PACKAGE_NAME_TOO_LONG); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + + /* a package is present - create and populate pinfo structure */ + + pinfo = (struct pinfo *)calloc(1, sizeof (struct pinfo)); + if (!pinfo) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_NO_MEMORY); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + if (!lastpinfo) { + ept->pinfo = pinfo; /* first one */ + } else { + lastpinfo->next = pinfo; /* link list */ + } + lastpinfo = pinfo; + + if ((pkgname[0] == '-') || (pkgname[0] == '+') || + (pkgname[0] == '*') || (pkgname[0] == '~') || + (pkgname[0] == '!') || (pkgname[0] == '%')) { + pinfo->status = pkgname[0]; + (void) strlcpy(pinfo->pkg, pkgname+1, + sizeof (pinfo->pkg)); + } else { + (void) strlcpy(pinfo->pkg, pkgname, + sizeof (pinfo->pkg)); + } + + /* pkg/[:[ftype][:class] */ + c = (vfpGetc(cfVfp)); + if (c == '\\') { + /* get alternate ftype */ + pinfo->editflag++; + c = (vfpGetc(cfVfp)); + } + + if (c == ':') { + /* get special classname */ + (void) getstr(&vfpGetCurrCharPtr(cfVfp), + sizeof (classname), classname, ISWORDSEP); + (void) strlcpy(pinfo->aclass, classname, + sizeof (pinfo->aclass)); + c = (vfpGetc(cfVfp)); + } + ept->npkgs++; + + /* break out of while if at end of entry */ + + if ((c == '\n') || (c == '\0')) { + break; + } + + /* if package not separated by a space return an error */ + + if (!isspace(c)) { + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + setErrstr(ERR_BAD_ENTRY_END); + findend(&vfpGetCurrCharPtr(cfVfp)); + return (-1); + } + } + + /* + * parsing of the entry is complete + */ + + /* copy path found to 'lpath' */ + COPYPATH(lpath, cpath_start, cpath_len); + + /* write out any skipped data before returning */ + if (dataSkipped && (cfTmpVfp != (VFP_T *)NULL)) { + WRITEDATA(cfTmpVfp, firstPos, lastPos); + } + + /* if not at the end of the entry, make it so */ + + if ((c != '\n') && (c != '\0')) { + if (getend(&vfpGetCurrCharPtr(cfVfp)) && ept->pinfo) { + setErrstr(ERR_EXTRA_TOKENS); + return (-1); + } + } + + return (1); +} + +static int +getstr(char **cp, int n, char *str, int separator[]) +{ + int c; + char *p = *cp; + char *p1; + size_t len; + + if (*p == '\0') { + return (1); + } + + /* leading white space ignored */ + + while (((c = *p) != '\0') && (isspace(*p++))) + ; + if ((c == '\0') || (c == '\n')) { + p--; + *cp = p; + return (1); /* nothing there */ + } + + p--; + + /* compute length based on delimiter found or not */ + + p1 = p; + while (separator[(int)*p1] == 0) { + p1++; + } + + len = (ptrdiff_t)p1 - (ptrdiff_t)p; + + /* if string will fit in result buffer copy string and return success */ + + if (len < n) { + (void) memcpy(str, p, len); + str[len] = '\0'; + p += len; + *cp = p; + return (0); + } + + /* result buffer too small; copy partial string, return error */ + (void) memcpy(str, p, n-1); + str[n-1] = '\0'; + p += n; + *cp = p; + return (-1); +} + +static int +getend(char **cp) +{ + int n; + char *p = *cp; + + n = 0; + + /* if at end of buffer return no more characters left */ + + if (*p == '\0') { + return (0); + } + + while ((*p != '\0') && (*p != '\n')) { + if (n == 0) { + if (!isspace(*p)) { + n++; + } + } + p++; + } + + *cp = ++p; + return (n); +} + +static void +findend(char **cp) +{ + char *p1; + char *p = *cp; + + /* if at end of buffer return no more characters left */ + + if (*p == '\0') { + return; + } + + /* find the end of the line */ + + p1 = strchr(p, '\n'); + + if (p1 != (char *)NULL) { + *cp = ++p1; + return; + } + + *cp = strchr(p, '\0'); +} diff --git a/usr/src/lib/libpkg/common/tputcfent.c b/usr/src/lib/libpkg/common/tputcfent.c new file mode 100644 index 0000000000..492f0335af --- /dev/null +++ b/usr/src/lib/libpkg/common/tputcfent.c @@ -0,0 +1,191 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <sys/types.h> +#include "pkgstrct.h" +#include "pkglocale.h" + +#define MSG_INVALID "invalid entry" + +void +tputcfent(struct cfent *ept, FILE *fp) +{ + int count, status; + char *pt; + struct pinfo *pinfo; + struct tm *timep; + char timeb[BUFSIZ]; + + if (ept->path == NULL) + return; + + (void) fprintf(fp, pkg_gt("Pathname: %s\n"), ept->path); + (void) fprintf(fp, pkg_gt("Type: ")); + + switch (ept->ftype) { + case 'f': + (void) fputs(pkg_gt("regular file\n"), fp); + break; + + case 'd': + (void) fputs(pkg_gt("directory\n"), fp); + break; + + case 'x': + (void) fputs(pkg_gt("exclusive directory\n"), fp); + break; + + case 'v': + (void) fputs(pkg_gt("volatile file\n"), fp); + break; + + case 'e': + (void) fputs(pkg_gt("editted file\n"), fp); + break; + + case 'p': + (void) fputs(pkg_gt("named pipe\n"), fp); + break; + + case 'i': + (void) fputs(pkg_gt("installation file\n"), fp); + break; + + case 'c': + case 'b': + (void) fprintf(fp, pkg_gt("%s special device\n"), + (ept->ftype == 'b') ? pkg_gt("block") : + pkg_gt("character")); + + if (ept->ainfo.major == BADMAJOR) + (void) fprintf(fp, pkg_gt("Major device number: %s\n"), + MSG_INVALID); + else + (void) fprintf(fp, pkg_gt("Major device number: %d\n"), + ept->ainfo.major); + + if (ept->ainfo.minor == BADMINOR) + (void) fprintf(fp, pkg_gt("Minor device number: %s\n"), + MSG_INVALID); + else + (void) fprintf(fp, pkg_gt("Minor device number: %d\n"), + ept->ainfo.minor); + + break; + + case 'l': + (void) fputs(pkg_gt("linked file\n"), fp); + pt = (ept->ainfo.local ? ept->ainfo.local : + (char *)pkg_gt("(unknown)")); + (void) fprintf(fp, pkg_gt("Source of link: %s\n"), pt); + break; + + case 's': + (void) fputs(pkg_gt("symbolic link\n"), fp); + pt = (ept->ainfo.local ? ept->ainfo.local : + (char *)pkg_gt("(unknown)")); + (void) fprintf(fp, pkg_gt("Source of link: %s\n"), pt); + break; + + default: + (void) fputs(pkg_gt("unknown\n"), fp); + break; + } + + if (!strchr("lsin", ept->ftype)) { + if (ept->ainfo.mode == BADMODE) + (void) fprintf(fp, pkg_gt("Expected mode: %s\n"), + "?"); + else + (void) fprintf(fp, pkg_gt("Expected mode: %04o\n"), + ept->ainfo.mode); + + (void) fprintf(fp, pkg_gt("Expected owner: %s\n"), + ept->ainfo.owner); + (void) fprintf(fp, pkg_gt("Expected group: %s\n"), + ept->ainfo.group); + } + if (strchr("?infv", ept->ftype)) { + (void) fprintf(fp, + pkg_gt("Expected file size (bytes): %llu\n"), + ept->cinfo.size); + (void) fprintf(fp, + pkg_gt("Expected sum(1) of contents: %ld\n"), + ept->cinfo.cksum); + if (ept->cinfo.modtime > 0) { + timep = localtime(&(ept->cinfo.modtime)); + strftime(timeb, sizeof (timeb), + pkg_gt("Expected last modification: %b %d %X %Y\n"), + timep); + (void) fprintf(fp, timeb); + } else + (void) fprintf(fp, + pkg_gt("Expected last modification: ?\n")); + } + if (ept->ftype == 'i') { + (void) fputc('\n', fp); + return; + } + + status = count = 0; + if ((pinfo = ept->pinfo) != NULL) { + (void) fprintf(fp, + pkg_gt("Referenced by the following packages:\n\t")); + while (pinfo) { + /* + * Check for partially installed object. Need + * to explicitly check for '!', because objects + * that are provided by a server will have a + * different status character. + */ + if (pinfo->status == '!') + status++; + (void) fprintf(fp, "%-15s", pinfo->pkg); + if ((++count % 5) == 0) { + (void) fputc('\n', fp); + (void) fputc('\t', fp); + count = 0; + } + pinfo = pinfo->next; + } + (void) fputc('\n', fp); + } + (void) fprintf(fp, pkg_gt("Current status: %s\n"), + status ? pkg_gt("partially installed") : + pkg_gt("installed")); + (void) fputc('\n', fp); +} diff --git a/usr/src/lib/libpkg/common/verify.c b/usr/src/lib/libpkg/common/verify.c new file mode 100644 index 0000000000..c48c5b8c77 --- /dev/null +++ b/usr/src/lib/libpkg/common/verify.c @@ -0,0 +1,989 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <utime.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <grp.h> +#include <pwd.h> +#include <errno.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <sys/mkdev.h> +#include "pkgstrct.h" +#include "pkglib.h" +#include "pkglibmsgs.h" +#include "pkglocale.h" + +#define WDMSK 0xFFFF +#define DATEFMT "%D %r" +#define LONG_BOUNDARY ((sizeof (unsigned long))-1) +#define CHUNK 1024*1024 + +static char theErrBuf[PATH_MAX+512] = {'\0'}; +static char *theErrStr = NULL; + +/* checksum disable switch */ +static int enable_checksum = 1; + +/* attribute disable flag */ +static int disable_attributes = 0; + +/* non-ABI symlinks supported */ +static int nonabi_symlinks; + +/* + * forward declarations + */ + +static int clear_target(char *path, char *ftype, int is_a_dir); + +unsigned long compute_checksum(int *r_err, char *path); + +/* union used to generate checksum */ +typedef union hilo { + struct part { + uint16_t hi; + uint16_t lo; + } hl; + uint32_t lg; +} CHECKSUM_T; + +/*PRINTFLIKE1*/ +static void +reperr(char *fmt, ...) +{ + char *pt; + ssize_t ptln; + va_list ap; + int n; + + if (fmt == (char *)NULL) { + theErrBuf[0] = '\0'; + } else { + if (n = strlen(theErrBuf)) { + pt = theErrBuf + n; + *pt++ = '\n'; + *pt = '\0'; + ptln = sizeof (theErrBuf)-n; + } else { + pt = theErrBuf; + ptln = sizeof (theErrBuf); + } + va_start(ap, fmt); + /* LINTED variable format specifier to vsnprintf() */ + (void) vsnprintf(pt, ptln, fmt, ap); + va_end(ap); + } +} + +/* + * Name: cverify + * Description: This function verifies and (if fix > 0) fixes the contents + * of the file at the path provided + * Arguments: fix - 0 - do not fix entries, 1 - fix entries + * ftype - single character "type" the entry is supposed to be + * path - path to file + * cinfo - content info structure representing the contents + * the entry is supposed to contain + * allow_checksum - determine if checksumming should be disabled: + * == 0 - do not perform checksum ever - override enable_checksum. + * != 0 - use the default checksum flag "enable_checksum" to + * determine if checksumming should be done. + * NOTE: modification and creation times can be repaired; the contents + * of the file cannot be corrected if the checksum indicates that + * the contents are not correct - VE_CONT will be returned in this + * case. + * Possible return values: + * - 0 = successful + * - VE_EXIST = path name does not exist + * - VE_FTYPE = path file type is not recognized, is not supported, + * or is not what was expected + * - VE_ATTR = path mode/group/user is not what was expected + * - VE_CONT = mod time/link target/major/minor/size/file system type/current + * directory is not what was expected + * - VE_FAIL = utime/target directory/link/stat/symlink/mknod/chmod/statvfs/ + * chown failed + */ + +int +cverify(int fix, char *ftype, char *path, struct cinfo *cinfo, + int allow_checksum) +{ + struct stat status; /* file status buffer */ + struct utimbuf times; + unsigned long mycksum; + int setval, retcode; + char tbuf1[512]; + char tbuf2[512]; + int cksumerr; + + setval = (*ftype == '?'); + retcode = 0; + reperr(NULL); + + if (stat(path, &status) < 0) { + reperr(pkg_gt(ERR_EXIST)); + return (VE_EXIST); + } + + /* -1 requires modtimes to be the same */ + /* 0 reports modtime failure */ + /* 1 fixes modtimes */ + + if (setval || (cinfo->modtime == BADCONT)) { + cinfo->modtime = status.st_mtime; + } else if (status.st_mtime != cinfo->modtime) { + if (fix > 0) { + /* reset times on the file */ + times.actime = cinfo->modtime; + times.modtime = cinfo->modtime; + if (utime(path, ×)) { + reperr(pkg_gt(ERR_MODFAIL)); + retcode = VE_FAIL; + } + } else if (fix < 0) { + /* modtimes must be the same */ + if (strftime(tbuf1, sizeof (tbuf1), DATEFMT, + localtime(&cinfo->modtime)) == 0) { + reperr(pkg_gt(ERR_MEM)); + } + if (strftime(tbuf2, sizeof (tbuf2), DATEFMT, + localtime(&status.st_mtime)) == 0) { + reperr(pkg_gt(ERR_MEM)); + } + reperr(pkg_gt(ERR_MTIME), tbuf1, tbuf2); + retcode = VE_CONT; + } + } + + if (setval || (cinfo->size == (fsblkcnt_t)BADCONT)) { + cinfo->size = status.st_size; + } else if (status.st_size != cinfo->size) { + if (!retcode) { + retcode = VE_CONT; + } + reperr(pkg_gt(ERR_SIZE), cinfo->size, status.st_size); + } + + cksumerr = 0; + + /* + * see if checksumming should be done: if checksumming is allowed, + * and checksumming is enabled, then checksum the file. + */ + + /* return if no need to compute checksum */ + + if ((allow_checksum == 0) || (enable_checksum == 0)) { + return (retcode); + } + + /* compute checksum */ + + mycksum = compute_checksum(&cksumerr, path); + + /* set value if not set or if checksum cannot be computed */ + + if (setval || (cinfo->cksum == BADCONT)) { + cinfo->cksum = mycksum; + return (retcode); + } + + /* report / return error if checksums mismatch or there is an error */ + + if ((mycksum != cinfo->cksum) || cksumerr) { + if (!retcode) { + retcode = VE_CONT; + } + if (!cksumerr) { + reperr(pkg_gt(ERR_CKSUM), cinfo->cksum, mycksum); + } + } + + return (retcode); +} + +/* + * Name: compute_checksum + * Description: generate checksum for specified file + * Arguments: r_cksumerr (int *) [RO, *RW] + * - pointer to integer that is set on return to: + * == 0 - no error occurred + * != 0 - error occurred + * a_path (char *) [RO, *RO] + * - pointer to string representing path to file to + * generate checksum of + * Returns: unsigned long - results: + * - If *r_cksumerr == 0, checksum of specified file + * - If *r_cksumerr != 0, undefined + */ +unsigned long +compute_checksum(int *r_cksumerr, char *a_path) +{ + CHECKSUM_T suma; /* to split four-bytes into 2 two-byte values */ + CHECKSUM_T tempa; + int fd; + uint32_t lg; /* running checksum value */ + uint32_t buf[CHUNK/4]; /* to read CHUNK bytes */ + uint32_t lsavhi; /* high order two-bytes of four-byte checksum */ + uint32_t lsavlo; /* low order two-bytes of four-byte checksum */ + int leap = sizeof (uint32_t); + int notyet = 0; + int nread; + struct stat64 sbuf; + + /* reset error flag */ + *r_cksumerr = 0; + + /* open file and obtain -> where file is mapped/read */ + if ((fd = open(a_path, O_RDONLY)) < 0) { + *r_cksumerr = 1; + reperr(pkg_gt(ERR_NO_CKSUM)); + perror(ERR_NO_CKSUM); + return (0); + } + + if (fstat64(fd, &sbuf) != 0) { + *r_cksumerr = 1; + reperr(pkg_gt(ERR_NO_CKSUM)); + perror(ERR_NO_CKSUM); + return (0); + } + + /* initialize checksum value */ + lg = 0; + + /* + * Read CHUNK bytes off the file at a time; Read size of long bytes + * from memory at a time and process them. + * If last read, then read remnant bytes and process individually. + */ + errno = 0; + while ((nread = read(fd, (void*)buf, + (sbuf.st_size < CHUNK) ? sbuf.st_size : CHUNK)) > 0) { + uchar_t *s; + uint32_t *p = buf; + + notyet = nread % leap; + nread -= notyet; + + for (; nread > 0; nread -= leap) { + lg += ((((*p)>>24)&0xFF) & WDMSK); + lg += ((((*p)>>16)&0xFF) & WDMSK); + lg += ((((*p)>>8)&0xFF) & WDMSK); + lg += (((*p)&0xFF) & WDMSK); + p++; + } + s = (uchar_t *)p; + /* leftover bytes less than four in number */ + while (notyet--) + lg += (((uint32_t)(*s++)) & WDMSK); + } + + /* wind up */ + (void) close(fd); + + /* compute checksum components */ + suma.lg = lg; + tempa.lg = (suma.hl.lo & WDMSK) + (suma.hl.hi & WDMSK); + lsavhi = (uint32_t)tempa.hl.hi; + lsavlo = (uint32_t)tempa.hl.lo; + + /* return final checksum value */ + return (lsavhi+lsavlo); +} + +static struct stat status; /* file status buffer */ +static struct statvfs vfsstatus; /* filesystem status buffer */ + +/* + * Remove the thing that's currently in place so we can put down the package + * object. If we're replacing a directory with a directory, leave it alone. + * Returns 1 if all OK and 0 if failed. + */ +static int +clear_target(char *path, char *ftype, int is_a_dir) +{ + int retcode = 1; + + if (is_a_dir) { /* if there's a directory there already ... */ + /* ... and this isn't, ... */ + if ((*ftype != 'd') && (*ftype != 'x')) { + if (rmdir(path)) { /* try to remove it. */ + reperr(pkg_gt(ERR_RMDIR), path); + retcode = 0; + } + } + } else { + if (remove(path)) { + if (errno != ENOENT) { + retcode = 0; /* It didn't work. */ + } + } + } + + return (retcode); +} + +/* + * Name: averify + * Description: This function verifies and (if fix > 0) fixes the attributes + * of the file at the path provided. + * Arguments: fix - 0 - do not fix entries, 1 - fix entries + * ftype - single character "type" the entry is supposed to be + * path - path to file + * ainfo - attribute info structure representing the attributes + * the entry is supposed to be + * NOTE: attributes are links and permissions + * Possible return values: + * - 0 = successful + * - VE_EXIST = path name does not exist + * - VE_FTYPE = path file type is not recognized, is not supported, + * or is not what was expected + * - VE_ATTR = path mode/group/user is not what was expected + * - VE_CONT = mod time/link target/major/minor/size/file system type/current + * directory is not what was expected + * - VE_FAIL = utime/target directory/link/stat/symlink/mknod/chmod/statvfs/ + * chown failed + */ +int +averify(int fix, char *ftype, char *path, struct ainfo *ainfo) +{ + struct group *grp; /* group entry buffer */ + struct passwd *pwd; + int n; + int setval; + int uid, gid; + int dochown; + int retcode; + int statError = 0; + int targ_is_dir = 0; /* replacing a directory */ + char myftype; + char buf[PATH_MAX]; + ino_t my_ino; + dev_t my_dev; + char cwd[MAXPATHLEN]; + char *cd; + char *c; + + setval = (*ftype == '?'); + retcode = 0; + reperr(NULL); + + if (get_disable_attribute_check()) { + return (0); + } + + if (*ftype == 'l') { + if (stat(path, &status) < 0) { + retcode = VE_EXIST; + reperr(pkg_gt(ERR_EXIST)); + } + + my_ino = status.st_ino; + my_dev = status.st_dev; + + /* Get copy of the current working directory */ + if (getcwd(cwd, MAXPATHLEN) == NULL) { + reperr(pkg_gt(ERR_GETWD), ainfo->local); + return (VE_FAIL); + } + + /* + * Change to the directory in which the hard + * link is to be created. + */ + cd = strdup(path); + c = strrchr(cd, '/'); + if (c) { + /* bugid 4247895 */ + if (strcmp(cd, c) == 0) + strcpy(cd, "/"); + else + *c = NULL; + + if (chdir(cd) != 0) { + reperr(pkg_gt(ERR_CHDIR), cd); + return (VE_FAIL); + } + } + free(cd); + + if (retcode || (status.st_nlink < 2) || + (stat(ainfo->local, &status) < 0) || + (my_dev != status.st_dev) || (my_ino != status.st_ino)) { + if (fix) { + /* + * Don't want to do a hard link to a + * directory. + */ + if (!isdir(ainfo->local)) { + chdir(cwd); + reperr(pkg_gt(ERR_LINKISDIR), + ainfo->local); + return (VE_FAIL); + } + /* Now do the link. */ + if (!clear_target(path, ftype, targ_is_dir)) + return (VE_FAIL); + + if (link(ainfo->local, path)) { + chdir(cwd); + reperr(pkg_gt(ERR_LINKFAIL), + ainfo->local); + return (VE_FAIL); + } + retcode = 0; + } else { + /* Go back to previous working directory */ + if (chdir(cwd) != 0) + reperr(pkg_gt(ERR_CHDIR), cwd); + + reperr(pkg_gt(ERR_LINK), ainfo->local); + return (VE_CONT); + } + } + + /* Go back to previous working directory */ + if (chdir(cwd) != 0) { + reperr(pkg_gt(ERR_CHDIR), cwd); + return (VE_CONT); + } + + return (retcode); + } + + retcode = 0; + + /* If we are to process symlinks the old way then we follow the link */ + if (nonABI_symlinks()) { + if ((*ftype == 's') ? lstat(path, &status) : + stat(path, &status)) { + reperr(pkg_gt(ERR_EXIST)); + retcode = VE_EXIST; + myftype = '?'; + statError++; + } + /* If not then we inspect the target of the link */ + } else { + if ((n = lstat(path, &status)) == -1) { + reperr(pkg_gt(ERR_EXIST)); + retcode = VE_EXIST; + myftype = '?'; + statError++; + } + } + if (!statError) { + /* determining actual type of existing object */ + switch (status.st_mode & S_IFMT) { + case S_IFLNK: + myftype = 's'; + break; + + case S_IFIFO: + myftype = 'p'; + break; + + case S_IFCHR: + myftype = 'c'; + break; + + case S_IFDIR: + myftype = 'd'; + targ_is_dir = 1; + break; + + case S_IFBLK: + myftype = 'b'; + break; + + case S_IFREG: + case 0: + myftype = 'f'; + break; + + case S_IFDOOR: + myftype = 'D'; + break; + + default: + reperr(pkg_gt(ERR_UNKNOWN)); + return (VE_FTYPE); + } + } + + if (setval) { + /* + * Check to make sure that a package or an installf that uses + * wild cards '?' to assume the ftype of an object on the + * system is not assuming a door ftype. Doors are not supported + * but should be ignored. + */ + if (myftype == 'D') { + reperr(pkg_gt(ERR_FTYPED), path); + retcode = VE_FTYPE; + return (VE_FTYPE); + } else { + *ftype = myftype; + } + } else if (!retcode && (*ftype != myftype) && + ((myftype != 'f') || !strchr("ilev", *ftype)) && + ((myftype != 'd') || (*ftype != 'x'))) { + reperr(pkg_gt(ERR_FTYPE), *ftype, myftype); + retcode = VE_FTYPE; + } + + if (!retcode && (*ftype == 's')) { + /* make sure that symbolic link is correct */ + n = readlink(path, buf, PATH_MAX); + if (n < 0) { + reperr(pkg_gt(ERR_SLINK), ainfo->local); + retcode = VE_CONT; + } else if (ainfo->local != NULL) { + buf[n] = '\0'; + if (strcmp(buf, ainfo->local)) { + reperr(pkg_gt(ERR_SLINK), ainfo->local); + retcode = VE_CONT; + } + } else if (ainfo->local == NULL) { + /* + * Since a sym link target exists, insert it + * into the ainfo structure + */ + buf[n] = '\0'; + ainfo->local = strdup(buf); + } + } + + if (retcode) { + /* The path doesn't exist or is different than it should be. */ + if (fix) { + /* + * Clear the way for the write. If it won't clear, + * there's nothing we can do. + */ + if (!clear_target(path, ftype, targ_is_dir)) + return (VE_FAIL); + + if ((*ftype == 'd') || (*ftype == 'x')) { + char *pt, *p; + + /* Try to make it the easy way */ + if (mkdir(path, ainfo->mode)) { + /* + * Failing that, walk through the + * parent directories creating + * whatever is needed. + */ + p = strdup(path); + pt = (*p == '/') ? p+1 : p; + do { + if (pt = strchr(pt, '/')) + *pt = '\0'; + if (access(p, 0) && + mkdir(p, ainfo->mode)) + break; + if (pt) + *pt++ = '/'; + } while (pt); + free(p); + } + if (stat(path, &status) < 0) { + reperr(pkg_gt(ERR_DIRFAIL)); + return (VE_FAIL); + } + } else if (*ftype == 's') { + if (symlink(ainfo->local, path)) { + reperr(pkg_gt(ERR_SLINKFAIL), + ainfo->local); + return (VE_FAIL); + } + + } else if (*ftype == 'c') { + int wilddevno = 0; + /* + * The next three if's support 2.4 and older + * packages that use "?" as device numbers. + * This should be considered for removal by + * release 2.7 or so. + */ + if (ainfo->major == BADMAJOR) { + ainfo->major = 0; + wilddevno = 1; + } + + if (ainfo->minor == BADMINOR) { + ainfo->minor = 0; + wilddevno = 1; + } + + if (wilddevno) { + wilddevno = 0; + logerr(MSG_WLDDEVNO, path, + ainfo->major, ainfo->minor); + } + + if (mknod(path, ainfo->mode | S_IFCHR, +#ifdef SUNOS41 + makedev(ainfo->xmajor, ainfo->xminor)) || +#else + makedev(ainfo->major, ainfo->minor)) || +#endif + (stat(path, &status) < 0)) { + reperr(pkg_gt(ERR_CDEVFAIL)); + return (VE_FAIL); + } + } else if (*ftype == 'b') { + int wilddevno = 0; + /* + * The next three if's support 2.4 and older + * packages that use "?" as device numbers. + * This should be considered for removal by + * release 2.7 or so. + */ + if (ainfo->major == BADMAJOR) { + ainfo->major = 0; + wilddevno = 1; + } + + if (ainfo->minor == BADMINOR) { + ainfo->minor = 0; + wilddevno = 1; + } + + if (wilddevno) { + wilddevno = 0; + logerr(MSG_WLDDEVNO, path, + ainfo->major, ainfo->minor); + } + + if (mknod(path, ainfo->mode | S_IFBLK, +#ifdef SUNOS41 + makedev(ainfo->xmajor, ainfo->xminor)) || +#else + makedev(ainfo->major, ainfo->minor)) || +#endif + (stat(path, &status) < 0)) { + reperr(pkg_gt(ERR_BDEVFAIL)); + return (VE_FAIL); + } + } else if (*ftype == 'p') { + if (mknod(path, ainfo->mode | S_IFIFO, NULL) || + (stat(path, &status) < 0)) { + reperr(pkg_gt(ERR_PIPEFAIL)); + return (VE_FAIL); + } + } else + return (retcode); + + } else + return (retcode); + } + + if (*ftype == 's') + return (0); /* don't check anything else */ + if (*ftype == 'i') + return (0); /* don't check anything else */ + + retcode = 0; + if ((myftype == 'c') || (myftype == 'b')) { +#ifdef SUNOS41 + if (setval || (ainfo->xmajor < 0)) + ainfo->xmajor = ((status.st_rdev>>8)&0377); + if (setval || (ainfo->xminor < 0)) + ainfo->xminor = (status.st_rdev&0377); + /* check major & minor */ + if (status.st_rdev != makedev(ainfo->xmajor, ainfo->xminor)) { + reperr(pkg_gt(ERR_MAJMIN), ainfo->xmajor, + ainfo->xminor, + (status.st_rdev>>8)&0377, status.st_rdev&0377); + retcode = VE_CONT; + } +#else + if (setval || (ainfo->major == BADMAJOR)) + ainfo->major = major(status.st_rdev); + if (setval || (ainfo->minor == BADMINOR)) + ainfo->minor = minor(status.st_rdev); + /* check major & minor */ + if (status.st_rdev != makedev(ainfo->major, ainfo->minor)) { + reperr(pkg_gt(ERR_MAJMIN), ainfo->major, ainfo->minor, + major(status.st_rdev), minor(status.st_rdev)); + retcode = VE_CONT; + } +#endif + } + + /* compare specified mode w/ actual mode excluding sticky bit */ + if (setval || (ainfo->mode == BADMODE) || (ainfo->mode == WILDCARD)) + ainfo->mode = status.st_mode & 07777; + else if ((ainfo->mode & 06777) != (status.st_mode & 06777)) { + if (fix) { + if ((ainfo->mode == BADMODE) || + (chmod(path, ainfo->mode) < 0)) + retcode = VE_FAIL; + } else { + reperr(pkg_gt(ERR_PERM), ainfo->mode, + status.st_mode & 07777); + if (!retcode) + retcode = VE_ATTR; + } + } + + dochown = 0; + + /* get group entry for specified group */ + if (setval || strcmp(ainfo->group, BADGROUP) == 0) { + grp = cgrgid(status.st_gid); + if (grp) + (void) strcpy(ainfo->group, grp->gr_name); + else { + if (!retcode) + retcode = VE_ATTR; + reperr(pkg_gt(ERR_BADGRPID), status.st_gid); + } + gid = status.st_gid; + } else if ((grp = cgrnam(ainfo->group)) == NULL) { + reperr(pkg_gt(ERR_BADGRPNM), ainfo->group); + if (!retcode) + retcode = VE_ATTR; + } else if ((gid = grp->gr_gid) != status.st_gid) { + if (fix) { + /* save specified GID */ + gid = grp->gr_gid; + dochown++; + } else { + if ((grp = cgrgid((int)status.st_gid)) == + (struct group *)NULL) { + reperr(pkg_gt(ERR_GROUP), ainfo->group, + "(null)"); + } else { + reperr(pkg_gt(ERR_GROUP), ainfo->group, + grp->gr_name); + } + if (!retcode) + retcode = VE_ATTR; + } + } + + /* get password entry for specified owner */ + if (setval || strcmp(ainfo->owner, BADOWNER) == 0) { + pwd = cpwuid((int)status.st_uid); + if (pwd) + (void) strcpy(ainfo->owner, pwd->pw_name); + else { + if (!retcode) + retcode = VE_ATTR; + reperr(pkg_gt(ERR_BADUSRID), status.st_uid); + } + uid = status.st_uid; + } else if ((pwd = cpwnam(ainfo->owner)) == NULL) { + /* UID does not exist in password file */ + reperr(pkg_gt(ERR_BADUSRNM), ainfo->owner); + if (!retcode) + retcode = VE_ATTR; + } else if ((uid = pwd->pw_uid) != status.st_uid) { + /* get owner name for actual UID */ + if (fix) { + uid = pwd->pw_uid; + dochown++; + } else { + pwd = cpwuid((int)status.st_uid); + if (pwd == NULL) + reperr(pkg_gt(ERR_BADUSRID), + (int)status.st_uid); + else + reperr(pkg_gt(ERR_OWNER), ainfo->owner, + pwd->pw_name); + + if (!retcode) + retcode = VE_ATTR; + } + } + + if (statvfs(path, &vfsstatus) < 0) { + reperr(pkg_gt(ERR_EXIST)); + retcode = VE_FAIL; + } else { + if (dochown) { + /* pcfs doesn't support file ownership */ + if (strcmp(vfsstatus.f_basetype, "pcfs") != 0 && + chown(path, uid, gid) < 0) { + retcode = VE_FAIL; /* chown failed */ + } + } + } + + if (retcode == VE_FAIL) + reperr(pkg_gt(ERR_ATTRFAIL)); + return (retcode); +} + +/* + * This is a special fast verify which basically checks the attributes + * and then, if all is OK, checks the size and mod time using the same + * stat and statvfs structures. + */ +int +fverify(int fix, char *ftype, char *path, struct ainfo *ainfo, + struct cinfo *cinfo) +{ + int retval; + + /* return success if attribute checks are disabled */ + + if (get_disable_attribute_check()) { + return (0); + } + + if ((retval = averify(fix, ftype, path, ainfo)) == 0) { + if (*ftype == 'f' || *ftype == 'i') { + if (cinfo->size != status.st_size) { + reperr(pkg_gt(WRN_QV_SIZE), path); + retval = VE_CONT; + } + /* pcfs doesn't support modification times */ + if (strcmp(vfsstatus.f_basetype, "pcfs") != 0) { + if (cinfo->modtime != status.st_mtime) { + reperr(pkg_gt(WRN_QV_MTIME), path); + retval = VE_CONT; + } + } + } + } + + return (retval); +} + +/* + * This function determines whether or not non-ABI symlinks are supported. + */ + +int +nonABI_symlinks(void) +{ + return (nonabi_symlinks); +} + +void +set_nonABI_symlinks(void) +{ + nonabi_symlinks = 1; +} + +/* + * Disable attribute checking. Only disable attribute checking if files + * are guaranteed to exist in the FS. + */ +void +disable_attribute_check(void) +{ + disable_attributes = 1; +} + +/* + * This function determines whether or not to do attribute checking. + * Returns: 0 - Do attribute checking + * !0 - Don't do attribute checking + */ +int +get_disable_attribute_check(void) +{ + return (disable_attributes); +} + +/* + * This function returns the address of the "global" error buffer that + * is populated by the various functions in this module. + */ + +char * +getErrbufAddr(void) +{ + return (theErrBuf); +} + +/* + * This function returns the size of the buffer returned by getErrbufAddr() + */ + +int +getErrbufSize(void) +{ + return (sizeof (theErrBuf)); +} + +/* + * This function returns the current global "error string" + */ + +char * +getErrstr(void) +{ + return (theErrStr); +} + +/* + * This function sets the global "error string" + */ + +void +setErrstr(char *a_errstr) +{ + theErrStr = a_errstr; +} + +/* + * This function enables checksumming + */ + +void +checksum_on(void) +{ + enable_checksum = 1; +} + +/* + * This function disables checksumming + */ + +void +checksum_off(void) +{ + enable_checksum = 0; +} diff --git a/usr/src/lib/libpkg/common/vfpops.c b/usr/src/lib/libpkg/common/vfpops.c new file mode 100644 index 0000000000..a4e6e54112 --- /dev/null +++ b/usr/src/lib/libpkg/common/vfpops.c @@ -0,0 +1,1283 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + + +/* + * Module: vfpops.c + * Synopsis: Implements virtual file protocol operations + * Description: + * + * This module implements the "Virtual File protocol" operations. These + * operations are intended to provide very fast access to file data, + * allowing a file to be accessed in very efficient ways with extremely + * low-cpu intensive operations. If possible file data is mapped directly + * into memory allowing the data to be accessed directly. If the data + * cannot be mapped directly into memory, memory will be allocated and + * the file data read directly into memory. If that fails currently the + * file data is not accessible. Other methods of making the file could + * be implemented in the future (e.g. stdio if all else fails). + * + * In general any code that uses stdio to access a file can be changed + * to use the various "vfp" operations to access a file, with a resulting + * increase in performance and decrease in cpu time required to access + * the file contents. + * + * Public Methods: + * + * vfpCheckpointFile - Create new VFP that checkpoints existing VFP + * vfpCheckpointOpen - open file, allocate storage, return pointer to VFP_T + * vfpClose - close file associated with vfp + * vfpDecCurrPtr - decrement current character pointer + * vfpGetBytesRemaining - get number of bytes remaining to read + * vfpGetCurrCharPtr - get pointer to current character + * vfpGetCurrPtrDelta - get number of bytes between current and specified char + * vfpGetFirstCharPtr - get pointer to first character + * vfpGetLastCharPtr - get pointer to last character + * vfpGetModifiedLen - get highest modified byte (length) contained in vfp + * vfpGetPath - get the path associated with the vfp + * vfpGetc - get current character and increment to next + * vfpGetcNoInc - get current character - do not increment + * vfpGets - get a string from the vfp into a fixed size buffer + * vfpIncCurrPtr - increment current character pointer + * vfpIncCurrPtrBy - increment current pointer by specified delta + * vfpOpen - open file on vfp + * vfpPutBytes - put fixed number of bytes to current character and increment + * vfpPutFormat - put format one arg to current character and increment + * vfpPutInteger - put integer to current character and increment + * vfpPutLong - put long to current character and increment + * vfpPutc - put current character and increment to next + * vfpPuts - put string to current character and increment + * vfpRewind - rewind file to first byte + * vfpSeekToEnd - seek to end of file + * vfpSetCurrCharPtr - set pointer to current character + * vfpSetFlags - set flags that affect file access + * vfpSetSize - set size of file (for writing) + * vfpTruncate - truncate file + * vfpWriteToFile - write data contained in vfp to specified file + */ + +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <errno.h> +#include <libintl.h> +#include "pkglib.h" +#include "pkgstrct.h" +#include "pkglocale.h" + +/* + * These are internal flags that occupy the high order byte of the VFPFLAGS_T + * flags element of the vfp. These flags may only occupy the high order order + * 16 bits of the 32-bit unsigned vfp "flags" object. + */ + +#define _VFP_MMAP 0x00010000 /* mmap used */ +#define _VFP_MALLOC 0x00020000 /* malloc used */ +#define _VFP_WRITE 0x00040000 /* file opened for write */ +#define _VFP_READ 0x00080000 /* file opened for reading */ +#define _VFP_MODIFIED 0x00100000 /* contents are marked modified */ + +/* path name given to "anonymous" (string) vfp */ + +#define VFP_ANONYMOUS_PATH "<<string>>" + +/* minimum size file to mmap (64mb) */ + +#define MIN_MMAP_SIZE (64*1024) + +/* + * ***************************************************************************** + * global external (public) functions + * ***************************************************************************** + */ + +/* + * Name: vfpOpen + * Description: Open file on vfp, allocate storage, return pointer to VFP_T + * that can be used to access/modify file contents. + * Arguments: VFP_T **r_vfp - pointer to pointer to VFP_T + * char *a_path - path of file to open and associate with this VFP. + * - if the path is (char *)NULL then no file is associated + * with this VFP - this is a way to create a fixed length + * string that can be manipulated with the VFP operators. + * Before the VFP can be used "vfpSetSize" must be called + * to set the size of the string buffer. + * char *a_mode - fopen mode to open the file with + * VFPFLAGS_T a_flags - one or more flags to control the operation: + * - VFP_NONE - no special flags + * - VFP_NEEDNOW - file data needed in memory now + * - VFP_SEQUENTIAL - memory will be sequentially accessed + * - VFP_RANDOM - memory will be randomly accessed + * - VFP_NOMMAP - do not use mmap to access file + * - VFP_NOMALLOC - do not use malloc to buffer file + * Returns: int == 0 - operation was successful + * != 0 - operation failed, errno contains reason + * Side Effects: r_vfp -- filled in with a pointer to a newly allocated vfp + * which can be used with the various vfp functions. + * errno -- contains system error number if return is != 0 + */ + +int +vfpOpen(VFP_T **r_vfp, char *a_path, char *a_mode, VFPFLAGS_T a_flags) +{ + FILE *fp = (FILE *)NULL; + VFP_T *vfp; + int lerrno; + struct stat statbuf; + int pagesize = getpagesize(); + + /* reset return VFP/FILE pointers */ + + (*r_vfp) = (VFP_T *)NULL; + + /* allocate pre-zeroed vfp object */ + + vfp = (VFP_T *)calloc(sizeof (VFP_T), 1); + if (vfp == (VFP_T *)NULL) { + return (-1); + } + + /* create "string" vfp if no path specified */ + + if (a_path == (char *)NULL) { + /* + * no path specified - no open file associated with vfp + * The vfp is initialized to all zeros - initialize just those + * values that need to be non-zero. + */ + + vfp->_vfpFlags = _VFP_MALLOC; + vfp->_vfpPath = strdup(VFP_ANONYMOUS_PATH); + (*r_vfp) = vfp; + return (0); + } + + /* + * path specified - associate open file with vfp; + * return an error if no path or mode specified + */ + + if (a_mode == (char *)NULL) { + errno = EFAULT; /* Bad address */ + (void) free(vfp); + return (-1); + } + + /* return an error if an empty path or mode specified */ + + if ((*a_path == '\0') || (*a_mode == '\0')) { + errno = EINVAL; /* Invalid argument */ + (void) free(vfp); + return (-1); + } + + /* open the file */ + + fp = fopen(a_path, a_mode); + if (fp == (FILE *)NULL) { + lerrno = errno; + (void) free(vfp); + errno = lerrno; + return (-1); + } + + /* Get the file size */ + + if (fstat(fileno(fp), &statbuf) != 0) { + lerrno = errno; + (void) fclose(fp); + (void) free(vfp); + errno = lerrno; + return (-1); + } + + /* + * Obtain access to existing file contents: + * -> plan a: map contents file into memory + * -> plan b: on failure just read into large buffer + */ + + /* attempt to mmap file if mmap is allowed */ + + vfp->_vfpStart = MAP_FAILED; /* assume map failed if not allowed */ + + /* + * if file is a regular file, and if mmap allowed, + * and (malloc not forbidden or size is > minumum size to mmap) + */ + + if ((S_ISREG(statbuf.st_mode)) && (!(a_flags & VFP_NOMMAP)) && + ((a_flags & VFP_NOMALLOC) || statbuf.st_size > MIN_MMAP_SIZE)) { + char *p; + /* set size to current size of file */ + + vfp->_vfpMapSize = statbuf.st_size; + + /* + * compute proper size for mapping for the file contents; + * add in one extra page so falling off end when file size is + * exactly modulo page size does not cause a page fault to + * guarantee that the end of the file contents will always + * contain a '\0' null character. + */ + + vfp->_vfpSize = (statbuf.st_size + pagesize + + (pagesize-(statbuf.st_size % pagesize))); + + /* + * mmap allowed: mmap file into memory + * first allocate space on top of which the mapping can be done; + * this way we can guarantee that if the mapping happens to be + * an exact multiple of a page size, that there will be at least + * one byte past the end of the mapping that can be accessed and + * that is guaranteed to be zero. + */ + + /* allocate backing space */ + + p = (char *)memalign(pagesize, vfp->_vfpSize); + if (p == (char *)NULL) { + vfp->_vfpStart = MAP_FAILED; + } else { + /* guarantee first byte after end of data is zero */ + + p[vfp->_vfpMapSize] = '\0'; + + /* map file on top of the backing space */ + + vfp->_vfpStart = mmap(p, vfp->_vfpMapSize, PROT_READ, + MAP_PRIVATE|MAP_FIXED, fileno(fp), (off_t)0); + + /* if mmap succeeded set mmap used flag in vfp */ + + if (vfp->_vfpStart != MAP_FAILED) { + vfp->_vfpFlags |= _VFP_MMAP; + } + } + } + + /* if map failed (or not allowed) attempt malloc (if allowed) */ + + if ((vfp->_vfpStart == MAP_FAILED) && (!(a_flags & VFP_NOMALLOC))) { + /* mmap failed - plan b: read directly into memory */ + ssize_t rlen; + + /* + * compute proper size for allocating storage for file contents; + * add in one extra page so falling off end when file size is + * exactly modulo page size does not cause a page fault to + * guarantee that the end of the file contents will always + * contain a '\0' null character. + */ + + vfp->_vfpSize = statbuf.st_size+pagesize; + + /* allocate buffer to hold file data */ + + vfp->_vfpStart = memalign((size_t)pagesize, vfp->_vfpSize); + if (vfp->_vfpStart == (char *)NULL) { + lerrno = errno; + (void) fclose(fp); + (void) free(vfp); + errno = lerrno; + return (-1); + } + + /* read the file into the buffer */ + + if (statbuf.st_size != 0) { + rlen = read(fileno(fp), vfp->_vfpStart, + statbuf.st_size); + if (rlen != statbuf.st_size) { + lerrno = errno; + if (lerrno == 0) { + lerrno = EIO; + } + (void) free(vfp->_vfpStart); + (void) fclose(fp); + (void) free(vfp); + errno = lerrno; + return (-1); + } + + /* assure last byte+1 is null character */ + + ((char *)vfp->_vfpStart)[statbuf.st_size] = '\0'; + } + + /* set malloc used flag in vfp */ + + vfp->_vfpFlags |= _VFP_MALLOC; + } + + /* if no starting address all read methods failed */ + + if (vfp->_vfpStart == MAP_FAILED) { + /* no mmap() - no read() - cannot allocate memory */ + (void) fclose(fp); + (void) free(vfp); + errno = ENOMEM; + return (-1); + } + + /* + * initialize vfp contents + */ + + /* _vfpCurr -> next byte to read */ + vfp->_vfpCurr = (char *)vfp->_vfpStart; + + /* _vfpEnd -> last data byte */ + vfp->_vfpEnd = (((char *)vfp->_vfpStart) + statbuf.st_size)-1; + + /* _vfpHighWater -> last byte written */ + vfp->_vfpHighWater = (char *)vfp->_vfpEnd; + + /* _vfpFile -> associated FILE* object */ + vfp->_vfpFile = fp; + + /* set flags as appropriate */ + + (void) vfpSetFlags(vfp, a_flags); + + /* retain path name */ + + vfp->_vfpPath = strdup(a_path ? a_path : ""); + + /* set read/write flags */ + + if (*a_mode == 'w') { + vfp->_vfpFlags |= _VFP_WRITE; + } + + if (*a_mode == 'r') { + vfp->_vfpFlags |= _VFP_READ; + } + + /* set return vfp pointer */ + + (*r_vfp) = vfp; + + /* All OK */ + + return (0); +} + +/* + * Name: vfpClose + * Description: Close an open vfp, causing any modified data to be written out + * to the file associated with the vfp. + * Arguments: VFP_T **r_vfp - pointer to pointer to VFP_T returned by vfpOpen + * Returns: int == 0 - operation was successful + * != 0 - operation failed, errno contains reason + * Side Effects: r_vfp is set to (VFP_T)NULL + */ + +int +vfpClose(VFP_T **r_vfp) +{ + int ret; + int lerrno; + VFP_T *vfp; + + /* return error if NULL VFP_T** provided */ + + if (r_vfp == (VFP_T **)NULL) { + errno = EFAULT; + return (-1); + } + + /* localize access to VFP_T */ + + vfp = *r_vfp; + + /* return successful if NULL VFP_T* provided */ + + if (vfp == (VFP_T *)NULL) { + return (0); + } + + /* reset return VFP_T* handle */ + + *r_vfp = (VFP_T *)NULL; + + /* + * if closing a file that is open for writing, commit all data if the + * backing memory is volatile and if there is a file open to write + * the data to. + */ + + if (vfp->_vfpFlags & _VFP_WRITE) { + if ((vfp->_vfpFlags & _VFP_MALLOC) && + (vfp->_vfpFile != (FILE *)NULL)) { + size_t len; + + /* determine number of bytes to write */ + len = vfpGetModifiedLen(vfp); + + /* if modified bytes present commit data to the file */ + if (len > 0) { + (void) vfpSafePwrite(fileno(vfp->_vfpFile), + vfp->_vfpStart, len, (off_t)0); + } + } + } + + /* deallocate any allocated storage/mappings/etc */ + + if (vfp->_vfpFlags & _VFP_MALLOC) { + (void) free(vfp->_vfpStart); + } else if (vfp->_vfpFlags & _VFP_MMAP) { + /* unmap the file mapping */ + + (void) munmap(vfp->_vfpStart, vfp->_vfpMapSize); + + /* free the backing allocation */ + + (void) free(vfp->_vfpStart); + } + + /* free up path */ + + (void) free(vfp->_vfpPath); + + /* close the file */ + + ret = 0; + if (vfp->_vfpFile != (FILE *)NULL) { + ret = fclose(vfp->_vfpFile); + lerrno = errno; + } + + /* deallocate the vfp itself */ + + (void) free(vfp); + + /* if the fclose() failed, return error and errno */ + + if (ret != 0) { + errno = lerrno; + return (-1); + } + + return (0); +} + +/* + * Name: vfpSetFlags + * Description: Modify operation of VFP according to flags specified + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to set flags + * VFPFLAGS_T a_flags - one or more flags to control the operation: + * - VFP_NEEDNOW - file data needed in memory now + * - VFP_SEQUENTIAL - file data sequentially accessed + * - VFP_RANDOM - file data randomly accessed + * Any other flags specified are silently ignored. + * Returns: int == 0 - operation was successful + * != 0 - operation failed, errno contains reason + */ + +int +vfpSetFlags(VFP_T *a_vfp, VFPFLAGS_T a_flags) +{ + /* return if no vfp specified */ + + if (a_vfp == (VFP_T *)NULL) { + return (0); + } + + /* if file data mapped into memory, apply vm flags */ + + if ((a_vfp->_vfpSize != 0) && (a_vfp->_vfpFlags & _VFP_MMAP)) { + /* mmap succeeded: properly advise vm system */ + + if (a_flags & VFP_NEEDNOW) { + /* advise vm system data is needed now */ + (void) madvise(a_vfp->_vfpStart, a_vfp->_vfpMapSize, + MADV_WILLNEED); + } + if (a_flags & VFP_SEQUENTIAL) { + /* advise vm system data access is sequential */ + (void) madvise(a_vfp->_vfpStart, a_vfp->_vfpSize, + MADV_SEQUENTIAL); + } + if (a_flags & VFP_RANDOM) { + /* advise vm system data access is random */ + (void) madvise(a_vfp->_vfpStart, a_vfp->_vfpSize, + MADV_RANDOM); + } + } + + return (0); +} + +/* + * Name: vfpRewind + * Description: Reset default pointer for next read/write to start of file data + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to rewind + * Returns: void + * Operation is always successful + */ + +void +vfpRewind(VFP_T *a_vfp) +{ + /* return if no vfp specified */ + + if (a_vfp == (VFP_T *)NULL) { + return; + } + + /* set high water mark of last modified data */ + + if (a_vfp->_vfpCurr > a_vfp->_vfpHighWater) { + a_vfp->_vfpHighWater = a_vfp->_vfpCurr; + } + + /* reset next character pointer to start of file data */ + + a_vfp->_vfpCurr = a_vfp->_vfpStart; +} + +/* + * Name: vfpSetSize + * Description: Set size of in-memory image associated with VFP + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to set + * size_t a_size - number of bytes to associatge with VFP + * Returns: int == 0 - operation was successful + * != 0 - operation failed, errno contains reason + * Side Effects: + * Currently only a file that is in malloc()ed memory can + * have its in-memory size changed. + * An error is returned If the file is mapped into memory. + * A file cannot be decreased in size - if the specified + * size is less than the current size, the operation is + * successful but no change in file size occurs. + * If no file is associated with the VFP (no "name" was + * given to vfpOpen) the first call to vfpSetSize allocates + * the initial size of the file data - effectively calling + * "malloc" to allocate the initial memory for the file data. + * Once an initial allocation has been made, subsequent calls + * to vfpSetSize are effectively a "realloc" of the existing + * file data. + * All existing file data is preserved. + */ + +int +vfpSetSize(VFP_T *a_vfp, size_t a_size) +{ + char *np; + size_t curSize; + + /* return if no vfp specified */ + + if (a_vfp == (VFP_T *)NULL) { + return (0); + } + + /* if malloc not used don't know how to set size right now */ + + if (!(a_vfp->_vfpFlags & _VFP_MALLOC)) { + return (-1); + } + + /* adjust size to reflect extra page of data maintained */ + + a_size += getpagesize(); + + /* if size is not larger than current nothing to do */ + + if (a_size <= a_vfp->_vfpSize) { + return (0); + } + + /* remember new size */ + + curSize = a_vfp->_vfpSize; + a_vfp->_vfpSize = a_size; + + /* allocate/reallocate memory as appropriate */ + + if (a_vfp->_vfpStart != (char *)NULL) { + np = (char *)realloc(a_vfp->_vfpStart, a_vfp->_vfpSize+1); + if (np == (char *)NULL) { + return (-1); + } + np[curSize-1] = '\0'; + } else { + np = (char *)malloc(a_vfp->_vfpSize+1); + if (np == (char *)NULL) { + return (-1); + } + np[0] = '\0'; + } + + /* make sure last allocated byte is a null */ + + np[a_vfp->_vfpSize] = '\0'; + + /* + * adjust all pointers to account for buffer address change + */ + + /* _vfpCurr -> next byte to read */ + a_vfp->_vfpCurr = (char *)(((ptrdiff_t)a_vfp->_vfpCurr - + (ptrdiff_t)a_vfp->_vfpStart) + np); + + /* _vfpHighWater -> last byte written */ + a_vfp->_vfpHighWater = (char *)(((ptrdiff_t)a_vfp->_vfpHighWater - + (ptrdiff_t)a_vfp->_vfpStart) + np); + + /* _vfpEnd -> last data byte */ + a_vfp->_vfpEnd = (np + a_vfp->_vfpSize)-1; + + /* _vfpStart -> first data byte */ + a_vfp->_vfpStart = np; + + return (0); +} + +/* + * Name: vfpTruncate + * Description: Truncate data associated with VFP + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to truncate + * Returns: void + * Operation is always successful. + * Side Effects: + * In memory data associated with file is believed to be empty. + * Actual memory associated with file is not affected. + * If a file is associated with the VFP, it is truncated. + */ + +void +vfpTruncate(VFP_T *a_vfp) +{ + /* return if no vfp specified */ + + if (a_vfp == (VFP_T *)NULL) { + return; + } + + /* + * reset all pointers so that no data is associated with file + */ + + /* current byte is start of data area */ + + a_vfp->_vfpCurr = a_vfp->_vfpStart; + + /* last byte written is start of data area */ + + a_vfp->_vfpHighWater = a_vfp->_vfpStart; + + /* current character is NULL */ + + *a_vfp->_vfpCurr = '\0'; + + /* if file associated with VFP, truncate actual file */ + + if (a_vfp->_vfpFile != (FILE *)NULL) { + (void) ftruncate(fileno(a_vfp->_vfpFile), 0); + } +} + +/* + * Name: vfpWriteToFile + * Description: Write data associated with VFP to specified file + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to write + * char *a_path - path of file to write file data to + * Returns: int == 0 - operation was successful + * != 0 - operation failed, errno contains reason + */ + +int +vfpWriteToFile(VFP_T *a_vfp, char *a_path) +{ + int fd; + int lerrno = 0; + size_t len; + ssize_t result = 0; + + /* return if no vfp specified */ + + if (a_vfp == (VFP_T *)NULL) { + errno = EFAULT; + return (-1); + } + + /* on buffer overflow generate error */ + + if ((a_vfp->_vfpOverflow != 0) || (vfpGetBytesAvailable(a_vfp) < 1)) { + errno = EFBIG; + return (-1); + } + + /* open file to write data to */ + + fd = open(a_path, O_WRONLY|O_CREAT|O_TRUNC, 0644); + if (fd < 0) { + return (-1); + } + + /* determine number of bytes to write */ + + len = vfpGetModifiedLen(a_vfp); + + /* + * if there is data associated with the file, write it out; + * if an error occurs, close the file and return failure. + */ + + if (len > 0) { + result = vfpSafeWrite(fd, a_vfp->_vfpStart, len); + if (result != len) { + /* error comitting data - return failure */ + lerrno = errno; + (void) close(fd); + errno = lerrno; + return (-1); + } + } + + /* close the file */ + + (void) close(fd); + + /* data committed to backing store - clear the modified flag */ + + (void) vfpClearModified(a_vfp); + + /* return success */ + + return (0); +} + +/* + * Name: vfpCheckpointFile + * Description: Create new VFP that checkpoints existing VFP, can be used by + * subsequent call to vfpCheckpointOpen to open a file using the + * existing in-memory cache of the contents of the file + * Arguments: VFP_T **r_cpVfp - pointer to pointer to VFP_T to be filled in + * with "checkpointed file" VFP (backing store) + * VFP_T **a_vfp - pointer to pointer to VFP_T returned by vfpOpen + * representing the VFP to checkpoint + * char *a_path - path to file that is the backing store for the + * in-memory data represented by a_vfp - used to verify + * that the data in memory is not out of date with respect + * to the backing store when vfpCheckpointOpen is called + * == (char *)NULL - use path associated with a_vfp + * that is, the backing store file in use + * Returns: int == 0 - operation was successful + * - r_destVfp contains a pointer to a new VFP that + * may be used in a subsequent call to + * vfpCheckpointOpen + * - the VFP referenced by *a_vfp is free()ed and + * must no longer be referenced + * != 0 - operation failed, errno contains reason + * - the VFP referenced by *a_vfp is not affected; + * the caller may continue to use it + * Notes: If the data of a VFP to checkpoint is mmap()ed then this method + * returns failure - only malloc()ed data VFPs can be + * checkpointed. + */ + +int +vfpCheckpointFile(VFP_T **r_cpVfp, VFP_T **a_vfp, char *a_path) +{ + VFP_T *vfp; /* newly allocated checkpointed VFP */ + VFP_T *avfp; /* local -> to a_vfp */ + struct stat statbuf; /* stat(2) info for backing store */ + + /* return error if NULL VFP_T** to checkpoint provided */ + + if (r_cpVfp == (VFP_T **)NULL) { + errno = EFAULT; + return (-1); + } + + /* reset return checkpoint VFP pointer */ + + (*r_cpVfp) = (VFP_T *)NULL; + + /* return error if no VFP to checkpoint specified */ + + if (a_vfp == (VFP_T **)NULL) { + errno = EFAULT; + return (-1); + } + + /* localize reference to a_vfp */ + + avfp = *a_vfp; + + /* return error if no VFP to checkpoint specified */ + + if (avfp == (VFP_T *)NULL) { + errno = EFAULT; + return (-1); + } + + /* on buffer overflow generate error */ + + if ((avfp->_vfpOverflow != 0) || (vfpGetBytesAvailable(avfp) < 1)) { + errno = EFBIG; + return (-1); + } + + /* no checkpointing is possible if the existing VFP is mmap()ed */ + + if (avfp->_vfpFlags & _VFP_MMAP) { + errno = EIO; + return (-1); + } + + /* if no path specified, grab it from the VFP to checkpoint */ + + if ((a_path == (char *)NULL) || (*a_path == '\0')) { + a_path = avfp->_vfpPath; + } + + /* backing store required: if VFP is "string" then this is an error */ + + if ((a_path == (char *)NULL) || + strcmp(a_path, VFP_ANONYMOUS_PATH) == 0) { + errno = EINVAL; + return (-1); + } + + /* Get the VFP to checkpoint (backing store) file size */ + + if (stat(a_path, &statbuf) != 0) { + return (-1); + } + + /* allocate storage for checkpointed VFP (to return) */ + + vfp = (VFP_T *)malloc(sizeof (VFP_T)); + if (vfp == (VFP_T *)NULL) { + return (-1); + } + + /* + * close any file that is on the VFP to checkpoint (backing store); + * subsequent processes can modify the backing store data, and + * then when vfpCheckpointOpen is called, either the in-memory + * cached data will be used (if backing store unmodified) or else + * the in-memory data is released and the backing store is used. + */ + + if (avfp->_vfpFile != (FILE *)NULL) { + (void) fclose(avfp->_vfpFile); + avfp->_vfpFile = (FILE *)NULL; + } + + /* free any path associated with VFP to checkpoint (backing store) */ + + if (avfp->_vfpPath != (char *)NULL) { + (void) free(avfp->_vfpPath); + avfp->_vfpPath = (char *)NULL; + } + + /* copy contents of VFP to checkpoint to checkpointed VFP */ + + memcpy(vfp, avfp, sizeof (VFP_T)); + + /* free contents of VFP to checkpoint */ + + (void) free(avfp); + + /* reset pointer to VFP that has been free'd */ + + *a_vfp = (VFP_T *)NULL; + + /* remember path associated with the checkpointed VFP (backing store) */ + + vfp->_vfpPath = strdup(a_path); + + /* save tokens that identify the backing store for the in-memory data */ + + vfp->_vfpCkDev = statbuf.st_dev; /* devid holding st_ino inode */ + vfp->_vfpCkIno = statbuf.st_ino; /* backing store inode */ + vfp->_vfpCkMtime = statbuf.st_mtime; /* last data modification */ + vfp->_vfpCkSize = statbuf.st_size; /* backing store size (bytes) */ + vfp->_vfpCkStBlocks = statbuf.st_blocks; /* blocks allocated to file */ + + /* pass checkpointed VFP to caller */ + + (*r_cpVfp) = vfp; + + /* success! */ + + return (0); +} + +/* + * Name: vfpCheckpointOpen + * Description: Open file on vfp, allocate storage, return pointer to VFP_T + * that can be used to access/modify file contents. If a VFP_T to + * a checkpointed VFP is passed in, and the in memory contents of + * the VFP are not out of date with respect to the backing store + * file, use the existing in-memory contents - otherwise, discard + * the in-memory contents and reopen and reread the file. + * Arguments: VFP_T **a_cpVfp - pointer to pointer to VFP_T that represents + * checkpointed VFP to use to open the file IF the contents + * of the backing store are identical to the in-memory data + * VFP_T **r_vfp - pointer to pointer to VFP_T to open file on + * char *a_path - path of file to open and associate with this VFP. + * - if the path is (char *)NULL then no file is associated + * with this VFP - this is a way to create a fixed length + * string that can be manipulated with the VFP operators. + * Before the VFP can be used "vfpSetSize" must be called + * to set the size of the string buffer. + * char *a_mode - fopen mode to open the file with + * VFPFLAGS_T a_flags - one or more flags to control the operation: + * - VFP_NONE - no special flags + * - VFP_NEEDNOW - file data needed in memory now + * - VFP_SEQUENTIAL - memory will be sequentially accessed + * - VFP_RANDOM - memory will be randomly accessed + * - VFP_NOMMAP - do not use mmap to access file + * - VFP_NOMALLOC - do not use malloc to buffer file + * Returns: int == 0 - operation was successful + * != 0 - operation failed, errno contains reason + * Side Effects: r_vfp -- filled in with a pointer to a newly allocated vfp + * which can be used with the various VFP functions. + * a_cpVfp -- contents reset to zero if used to open the file + * errno -- contains system error number if return is != 0 + */ + +int +vfpCheckpointOpen(VFP_T **a_cpVfp, VFP_T **r_vfp, char *a_path, + char *a_mode, VFPFLAGS_T a_flags) +{ + FILE *fp; /* backing store */ + VFP_T *cpVfp; /* local -> to a_cpVfp checkpointed VFP */ + VFP_T *vfp; /* new VFP open on checkpointed backing store */ + struct stat statbuf; /* stat(2) info on backing store */ + + /* + * if no source VFP, or source VFP empty, + * or no backing store, just open file + */ + + if ((a_cpVfp == (VFP_T **)NULL) || (*a_cpVfp == (VFP_T *)NULL) || + ((*a_cpVfp)->_vfpStart == (char *)NULL)) { + (void) vfpClose(a_cpVfp); + return (vfpOpen(r_vfp, a_path, a_mode, a_flags)); + } + + /* localize access to checkpointed VFP_T (*a_cpVfp) */ + + cpVfp = *a_cpVfp; + + /* if no path specified, grab it from the checkpointed VFP */ + + if ((a_path == (char *)NULL) || (*a_path == '\0')) { + a_path = cpVfp->_vfpPath; + } + + /* return error if no path specified and no path in checkpointed VFP */ + + if ((a_path == (char *)NULL) && (*a_path == '\0')) { + errno = EINVAL; + return (-1); + } + + /* if no backing store path, then just open file */ + + if (stat(a_path, &statbuf) != 0) { + (void) vfpClose(a_cpVfp); + return (vfpOpen(r_vfp, a_path, a_mode, a_flags)); + } + + /* + * if backing store tokens do not match checkpointed VFP, + * the backing store has been updated since the VFP was checkpointed; + * release the in-memory data, and open and read the backing store + */ + + if ((statbuf.st_size != cpVfp->_vfpCkSize) || + (statbuf.st_mtime != cpVfp->_vfpCkMtime) || + (statbuf.st_blocks != cpVfp->_vfpCkStBlocks) || + (statbuf.st_ino != cpVfp->_vfpCkIno) || + (statbuf.st_dev != cpVfp->_vfpCkDev)) { + (void) vfpClose(a_cpVfp); + return (vfpOpen(r_vfp, a_path, a_mode, a_flags)); + } + + /* + * backing store has not been updated since the VFP was checkpointed; + * use the in-memory data without re-reading the backing store; open the + * backing store file (if no file already open on the checkpointed VFP) + * so there is an open file associated with the in-memory data + */ + + fp = cpVfp->_vfpFile; + if (fp == (FILE *)NULL) { + fp = fopen(a_path, a_mode); + if (fp == (FILE *)NULL) { + int lerrno; + + lerrno = errno; + (void) vfpClose(a_cpVfp); + errno = lerrno; + return (-1); + } + } + + /* allocate new VFP object to return as open VFP */ + + vfp = (VFP_T *)malloc(sizeof (VFP_T)); + if (vfp == (VFP_T *)NULL) { + (void) vfpClose(a_cpVfp); + return (vfpOpen(r_vfp, a_path, a_mode, a_flags)); + } + + /* copy cached checkpointed VFP to new VFP to return */ + + (void) memcpy(vfp, cpVfp, sizeof (VFP_T)); + + /* + * initialize VFP to return contents + */ + + /* FILE -> file opened on the VFPs backing store */ + + vfp->_vfpFile = fp; + + /* release any existing path associated with the VFP */ + + if (vfp->_vfpPath != (char *)NULL) { + (void) free(vfp->_vfpPath); + } + + /* path associated with the backing store for this VFP */ + + vfp->_vfpPath = strdup(a_path); + + /* + * data pointers associated with in memory copy of backing store + * (such as _vfpHighWater, _vfpEnd, _vfpStart, etc.) + * do not need to be modified because we are using the same backing + * store as was checkpointed in cpVfp that is pointed to by vfp. + */ + + /* _vfpCurr -> next byte to read */ + vfp->_vfpCurr = (char *)vfp->_vfpStart; + + /* free checkpointed VFP as it is now open on "vfp" */ + + (void) free(cpVfp); + + /* reset callers -> checkpointed VFP */ + + (*a_cpVfp) = (VFP_T *)NULL; + + /* set return VFP pointer */ + + (*r_vfp) = vfp; + + /* success! */ + + return (0); +} + +/* + * Name: vfpClearModified + * Description: Clear the "data is modified" indication from the VFP + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to clear + * the "data is modified" indication + * Returns: int - previous setting of "data is modified" indication + * == 0 - "data is modified" was NOT previously set + * != 0 - "data is modified" WAS previously set + */ + +int +vfpClearModified(VFP_T *a_vfp) +{ + VFPFLAGS_T flags; + + /* save current flags settings */ + + flags = a_vfp->_vfpFlags; + + /* clear "data is modified" flag */ + + a_vfp->_vfpFlags &= (~_VFP_MODIFIED); + + /* return previous "data is modified" flag setting */ + + return ((flags & _VFP_MODIFIED) != 0); +} + +/* + * Name: vfpSetModified + * Description: Set the "data is modified" indication from the VFP + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to set + * the "data is modified" indication + * Returns: int - previous setting of "data is modified" indication + * == 0 - "data is modified" was NOT previously set + * != 0 - "data is modified" WAS previously set + */ + +int +vfpSetModified(VFP_T *a_vfp) +{ + VFPFLAGS_T flags; + + /* save current flags settings */ + + flags = a_vfp->_vfpFlags; + + /* set "data is modified" flag */ + + a_vfp->_vfpFlags |= _VFP_MODIFIED; + + /* return previous "data is modified" flag setting */ + + return ((flags & _VFP_MODIFIED) != 0); +} + +/* + * Name: vfpGetModified + * Description: Get the "data is modified" indication from the VFP + * Arguments: VFP_T *a_vfp - VFP_T pointer associated with file to get + * the "data is modified" indication + * Returns: int - current setting of "data is modified" indication + * == 0 - "data is modified" is NOT set + * != 0 - "data is modified" IS set + */ + +int +vfpGetModified(VFP_T *a_vfp) +{ + /* return current "data is modified" flag setting */ + + return ((a_vfp->_vfpFlags & _VFP_MODIFIED) != 0); +} + +/* + * Name: vfpSafeWrite + * Description: write data to open file safely + * Arguments: a_fildes - file descriptor to write data to + * a_buf - pointer to buffer containing data to write + * a_nbyte - number of bytes to write to open file + * Returns: int + * < 0 - error, errno set + * >= 0 - success + * NOTE: unlike write(2), vfpSafeWrite() handles partial writes, and will + * ----- restart the write() until all bytes are written, or an error occurs. + */ + +ssize_t +vfpSafeWrite(int a_fildes, void *a_buf, size_t a_nbyte) +{ + ssize_t r; + size_t bytes = a_nbyte; + + for (;;) { + /* write bytes to file */ + r = write(a_fildes, a_buf, a_nbyte); + + /* return error on failure of write() */ + if (r < 0) { + /* EAGAIN: try again */ + if (errno == EAGAIN) { + continue; + } + /* EINTR: interrupted - try again */ + if (errno == EINTR) { + continue; + } + return (r); + } + + /* return total bytes written on success */ + if (r >= a_nbyte) { + return (bytes); + } + + /* partial write, adjust pointers, call write again */ + a_buf = (void *)((ptrdiff_t)a_buf + (ptrdiff_t)r); + a_nbyte -= (size_t)r; + } +} + +/* + * Name: vfpSafePwrite + * Description: write data to open file safely + * Arguments: a_fildes - file descriptor to write data to + * a_buf - pointer to buffer containing data to write + * a_nbyte - number of bytes to write to open file + * a_offset - offset into open file to write the first byte to + * Returns: int + * < 0 - error, errno set + * >= 0 - success + * NOTE: unlike pwrite(2), vfpSafePwrite() handles partial writes, and will + * ----- restart the pwrite() until all bytes are written, or an error occurs. + */ + +ssize_t +vfpSafePwrite(int a_fildes, void *a_buf, size_t a_nbyte, off_t a_offset) +{ + ssize_t r; + size_t bytes = a_nbyte; + + for (;;) { + /* write bytes to file */ + r = pwrite(a_fildes, a_buf, a_nbyte, a_offset); + + /* return error on failure of write() */ + if (r < 0) { + /* EAGAIN: try again */ + if (errno == EAGAIN) { + continue; + } + /* EINTR: interrupted - try again */ + if (errno == EINTR) { + continue; + } + return (r); + } + + /* return total bytes written on success */ + if (r >= a_nbyte) { + return (bytes); + } + + /* partial write, adjust pointers, call write again */ + a_buf = (void *)((ptrdiff_t)a_buf + (ptrdiff_t)r); + a_nbyte -= (size_t)r; + a_offset += (off_t)r; + } +} |