xref: /openbsd-src/usr.sbin/acme-client/fileproc.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$Id: fileproc.c,v 1.6 2016/09/13 17:13:37 deraadt Exp $ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <err.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
27 
28 #include "extern.h"
29 
30 static int
31 serialise(const char *tmp, const char *real,
32     const char *v, size_t vsz, const char *v2, size_t v2sz)
33 {
34 	int	 fd;
35 
36 	/*
37 	 * Write into backup location, overwriting.
38 	 * Then atomically (?) do the rename.
39 	 */
40 
41 	fd = open(tmp, O_WRONLY|O_CREAT|O_TRUNC, 0444);
42 	if (-1 == fd) {
43 		warn("%s", tmp);
44 		return (0);
45 	} else if ((ssize_t)vsz != write(fd, v, vsz)) {
46 		warnx("%s", tmp);
47 		close(fd);
48 		return (0);
49 	} else if (NULL != v2 && (ssize_t)v2sz != write(fd, v2, v2sz)) {
50 		warnx("%s", tmp);
51 		close(fd);
52 		return (0);
53 	} else if (-1 == close(fd)) {
54 		warn("%s", tmp);
55 		return (0);
56 	} else if (-1 == rename(tmp, real)) {
57 		warn("%s", real);
58 		return (0);
59 	}
60 
61 	return (1);
62 }
63 
64 int
65 fileproc(int certsock, int backup, const char *certdir)
66 {
67 	char		*csr = NULL, *ch = NULL;
68 	char		 file[PATH_MAX];
69 	size_t		 chsz, csz;
70 	int		 rc = 0;
71 	long		 lval;
72 	enum fileop	 op;
73 	time_t		 t;
74 
75 	/* File-system and sandbox jailing. */
76 
77 	if (chroot(certdir) == -1) {
78 		warn("chroot");
79 		goto out;
80 	}
81 	if (chdir("/") == -1) {
82 		warn("chdir");
83 		goto out;
84 	}
85 
86 	/*
87 	 * rpath and cpath for rename, wpath and cpath for
88 	 * writing to the temporary.
89 	 */
90 	if (pledge("stdio cpath wpath rpath", NULL) == -1) {
91 		warn("pledge");
92 		goto out;
93 	}
94 
95 	/* Read our operation. */
96 
97 	op = FILE__MAX;
98 	if (0 == (lval = readop(certsock, COMM_CHAIN_OP)))
99 		op = FILE_STOP;
100 	else if (FILE_CREATE == lval || FILE_REMOVE == lval)
101 		op = lval;
102 
103 	if (FILE_STOP == op) {
104 		rc = 1;
105 		goto out;
106 	} else if (FILE__MAX == op) {
107 		warnx("unknown operation from certproc");
108 		goto out;
109 	}
110 
111 	/*
112 	 * If we're backing up, then copy all files (found) by linking
113 	 * them to the file followed by the epoch in seconds.
114 	 * If we're going to remove, the unlink(2) will cause the
115 	 * original to go away.
116 	 * If we're going to update, the rename(2) will replace the
117 	 * certificate, leaving the backup as the only one.
118 	 */
119 
120 	if (backup) {
121 		t = time(NULL);
122 		snprintf(file, sizeof(file),
123 			"cert-%llu.pem", (unsigned long long)t);
124 		if (-1 == link(CERT_PEM, file) && ENOENT != errno) {
125 			warnx("%s/%s", certdir, CERT_PEM);
126 			goto out;
127 		} else
128 			dodbg("%s/%s: linked to %s", certdir, CERT_PEM, file);
129 
130 		snprintf(file, sizeof(file),
131 			"chain-%llu.pem", (unsigned long long)t);
132 		if (-1 == link(CHAIN_PEM, file) && ENOENT != errno) {
133 			warnx("%s/%s", certdir, CHAIN_PEM);
134 			goto out;
135 		} else
136 			dodbg("%s/%s: linked to %s", certdir, CHAIN_PEM, file);
137 
138 		snprintf(file, sizeof(file),
139 			"fullchain-%llu.pem", (unsigned long long)t);
140 		if (-1 == link(FCHAIN_PEM, file) && ENOENT != errno) {
141 			warnx("%s/%s", certdir, FCHAIN_PEM);
142 			goto out;
143 		} else
144 			dodbg("%s/%s: linked to %s", certdir, FCHAIN_PEM, file);
145 	}
146 
147 	/*
148 	 * If revoking certificates, just unlink the files.
149 	 * We return the special error code of 2 to indicate that the
150 	 * certificates were removed.
151 	 */
152 
153 	if (FILE_REMOVE == op) {
154 		if (-1 == unlink(CERT_PEM) && ENOENT != errno) {
155 			warn("%s/%s", certdir, CERT_PEM);
156 			goto out;
157 		} else
158 			dodbg("%s/%s: unlinked", certdir, CERT_PEM);
159 
160 		if (-1 == unlink(CHAIN_PEM) && ENOENT != errno) {
161 			warn("%s/%s", certdir, CHAIN_PEM);
162 			goto out;
163 		} else
164 			dodbg("%s/%s: unlinked", certdir, CHAIN_PEM);
165 
166 		if (-1 == unlink(FCHAIN_PEM) && ENOENT != errno) {
167 			warn("%s/%s", certdir, FCHAIN_PEM);
168 			goto out;
169 		} else
170 			dodbg("%s/%s: unlinked", certdir, FCHAIN_PEM);
171 
172 		rc = 2;
173 		goto out;
174 	}
175 
176 	/*
177 	 * Start by downloading the chain PEM as a buffer.
178 	 * This is not nil-terminated, but we're just going to guess
179 	 * that it's well-formed and not actually touch the data.
180 	 * Once downloaded, dump it into CHAIN_BAK.
181 	 */
182 
183 	if (NULL == (ch = readbuf(certsock, COMM_CHAIN, &chsz)))
184 		goto out;
185 	if (!serialise(CHAIN_BAK, CHAIN_PEM, ch, chsz, NULL, 0))
186 		goto out;
187 
188 	dodbg("%s/%s: created", certdir, CHAIN_PEM);
189 
190 	/*
191 	 * Next, wait until we receive the DER encoded (signed)
192 	 * certificate from the network process.
193 	 * This comes as a stream of bytes: we don't know how many, so
194 	 * just keep downloading.
195 	 */
196 
197 	if (NULL == (csr = readbuf(certsock, COMM_CSR, &csz)))
198 		goto out;
199 	if (!serialise(CERT_BAK, CERT_PEM, csr, csz, NULL, 0))
200 		goto out;
201 
202 	dodbg("%s/%s: created", certdir, CERT_PEM);
203 
204 	/*
205 	 * Finally, create the full-chain file.
206 	 * This is just the concatenation of the certificate and chain.
207 	 * We return the special error code 2 to indicate that the
208 	 * on-file certificates were changed.
209 	 */
210 
211 	if (!serialise(FCHAIN_BAK, FCHAIN_PEM, csr, csz, ch, chsz))
212 		goto out;
213 
214 	dodbg("%s/%s: created", certdir, FCHAIN_PEM);
215 
216 	rc = 2;
217 out:
218 	close(certsock);
219 	free(csr);
220 	free(ch);
221 	return (rc);
222 }
223