1*a629fefcSchristos /* $NetBSD: sftp-common.c,v 1.14 2023/10/25 20:19:57 christos Exp $ */
2*a629fefcSchristos /* $OpenBSD: sftp-common.c,v 1.34 2023/03/31 04:00:37 djm Exp $ */
317418e98Schristos
4ca32bd8dSchristos /*
5ca32bd8dSchristos * Copyright (c) 2001 Markus Friedl. All rights reserved.
6ca32bd8dSchristos * Copyright (c) 2001 Damien Miller. All rights reserved.
7ca32bd8dSchristos *
8ca32bd8dSchristos * Redistribution and use in source and binary forms, with or without
9ca32bd8dSchristos * modification, are permitted provided that the following conditions
10ca32bd8dSchristos * are met:
11ca32bd8dSchristos * 1. Redistributions of source code must retain the above copyright
12ca32bd8dSchristos * notice, this list of conditions and the following disclaimer.
13ca32bd8dSchristos * 2. Redistributions in binary form must reproduce the above copyright
14ca32bd8dSchristos * notice, this list of conditions and the following disclaimer in the
15ca32bd8dSchristos * documentation and/or other materials provided with the distribution.
16ca32bd8dSchristos *
17ca32bd8dSchristos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18ca32bd8dSchristos * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19ca32bd8dSchristos * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20ca32bd8dSchristos * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21ca32bd8dSchristos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22ca32bd8dSchristos * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23ca32bd8dSchristos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24ca32bd8dSchristos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25ca32bd8dSchristos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26ca32bd8dSchristos * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27ca32bd8dSchristos */
28ca32bd8dSchristos
29313c6c94Schristos #include "includes.h"
30*a629fefcSchristos __RCSID("$NetBSD: sftp-common.c,v 1.14 2023/10/25 20:19:57 christos Exp $");
31ee85abc4Schristos
32e4d43b82Schristos #include <sys/param.h> /* MAX */
33ca32bd8dSchristos #include <sys/types.h>
34ca32bd8dSchristos #include <sys/stat.h>
35ca32bd8dSchristos
36ca32bd8dSchristos #include <grp.h>
37ca32bd8dSchristos #include <pwd.h>
38ca32bd8dSchristos #include <stdio.h>
39ca32bd8dSchristos #include <string.h>
40ca32bd8dSchristos #include <time.h>
41ca32bd8dSchristos #include <stdarg.h>
42313c6c94Schristos #include <unistd.h>
438a4530f9Schristos #include <stdlib.h>
4434b27b53Sadam #include <util.h>
45ca32bd8dSchristos
46ca32bd8dSchristos #include "xmalloc.h"
47e4d43b82Schristos #include "ssherr.h"
48e4d43b82Schristos #include "sshbuf.h"
49ca32bd8dSchristos #include "log.h"
50ee85abc4Schristos #include "misc.h"
51ca32bd8dSchristos
52ca32bd8dSchristos #include "sftp.h"
53ca32bd8dSchristos #include "sftp-common.h"
54aef795aaSadam #include "fmt_scaled.h"
55ca32bd8dSchristos
56ca32bd8dSchristos /* Clear contents of attributes structure */
57ca32bd8dSchristos void
attrib_clear(Attrib * a)58ca32bd8dSchristos attrib_clear(Attrib *a)
59ca32bd8dSchristos {
60ca32bd8dSchristos a->flags = 0;
61ca32bd8dSchristos a->size = 0;
62ca32bd8dSchristos a->uid = 0;
63ca32bd8dSchristos a->gid = 0;
64ca32bd8dSchristos a->perm = 0;
65ca32bd8dSchristos a->atime = 0;
66ca32bd8dSchristos a->mtime = 0;
67ca32bd8dSchristos }
68ca32bd8dSchristos
69ca32bd8dSchristos /* Convert from struct stat to filexfer attribs */
70ca32bd8dSchristos void
stat_to_attrib(const struct stat * st,Attrib * a)71ca32bd8dSchristos stat_to_attrib(const struct stat *st, Attrib *a)
72ca32bd8dSchristos {
73ca32bd8dSchristos attrib_clear(a);
74ca32bd8dSchristos a->flags = 0;
75ca32bd8dSchristos a->flags |= SSH2_FILEXFER_ATTR_SIZE;
76ca32bd8dSchristos a->size = st->st_size;
77ca32bd8dSchristos a->flags |= SSH2_FILEXFER_ATTR_UIDGID;
78ca32bd8dSchristos a->uid = st->st_uid;
79ca32bd8dSchristos a->gid = st->st_gid;
80ca32bd8dSchristos a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
81ca32bd8dSchristos a->perm = st->st_mode;
82ca32bd8dSchristos a->flags |= SSH2_FILEXFER_ATTR_ACMODTIME;
83ca32bd8dSchristos a->atime = st->st_atime;
84ca32bd8dSchristos a->mtime = st->st_mtime;
85ca32bd8dSchristos }
86ca32bd8dSchristos
87ca32bd8dSchristos /* Convert from filexfer attribs to struct stat */
88ca32bd8dSchristos void
attrib_to_stat(const Attrib * a,struct stat * st)89ca32bd8dSchristos attrib_to_stat(const Attrib *a, struct stat *st)
90ca32bd8dSchristos {
91ca32bd8dSchristos memset(st, 0, sizeof(*st));
92ca32bd8dSchristos
93ca32bd8dSchristos if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
94ca32bd8dSchristos st->st_size = a->size;
95ca32bd8dSchristos if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
96ca32bd8dSchristos st->st_uid = a->uid;
97ca32bd8dSchristos st->st_gid = a->gid;
98ca32bd8dSchristos }
99ca32bd8dSchristos if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
100ca32bd8dSchristos st->st_mode = a->perm;
101ca32bd8dSchristos if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
102ca32bd8dSchristos st->st_atime = a->atime;
103ca32bd8dSchristos st->st_mtime = a->mtime;
104ca32bd8dSchristos }
105ca32bd8dSchristos }
106ca32bd8dSchristos
107ca32bd8dSchristos /* Decode attributes in buffer */
108e4d43b82Schristos int
decode_attrib(struct sshbuf * b,Attrib * a)109e4d43b82Schristos decode_attrib(struct sshbuf *b, Attrib *a)
110ca32bd8dSchristos {
111e4d43b82Schristos int r;
112ca32bd8dSchristos
113e4d43b82Schristos attrib_clear(a);
114e4d43b82Schristos if ((r = sshbuf_get_u32(b, &a->flags)) != 0)
115e4d43b82Schristos return r;
116e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
117e4d43b82Schristos if ((r = sshbuf_get_u64(b, &a->size)) != 0)
118e4d43b82Schristos return r;
119ca32bd8dSchristos }
120e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
121e4d43b82Schristos if ((r = sshbuf_get_u32(b, &a->uid)) != 0 ||
122e4d43b82Schristos (r = sshbuf_get_u32(b, &a->gid)) != 0)
123e4d43b82Schristos return r;
124e4d43b82Schristos }
125e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
126e4d43b82Schristos if ((r = sshbuf_get_u32(b, &a->perm)) != 0)
127e4d43b82Schristos return r;
128e4d43b82Schristos }
129e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
130e4d43b82Schristos if ((r = sshbuf_get_u32(b, &a->atime)) != 0 ||
131e4d43b82Schristos (r = sshbuf_get_u32(b, &a->mtime)) != 0)
132e4d43b82Schristos return r;
133ca32bd8dSchristos }
134ca32bd8dSchristos /* vendor-specific extensions */
135e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_EXTENDED) {
136e4d43b82Schristos char *type;
137e4d43b82Schristos u_char *data;
138e4d43b82Schristos size_t dlen;
139e4d43b82Schristos u_int i, count;
140ca32bd8dSchristos
141e4d43b82Schristos if ((r = sshbuf_get_u32(b, &count)) != 0)
14217418e98Schristos return r;
143*a629fefcSchristos if (count > 0x100000)
144*a629fefcSchristos return SSH_ERR_INVALID_FORMAT;
145ca32bd8dSchristos for (i = 0; i < count; i++) {
146e4d43b82Schristos if ((r = sshbuf_get_cstring(b, &type, NULL)) != 0 ||
147e4d43b82Schristos (r = sshbuf_get_string(b, &data, &dlen)) != 0)
148e4d43b82Schristos return r;
149e4d43b82Schristos debug3("Got file attribute \"%.100s\" len %zu",
150e4d43b82Schristos type, dlen);
15100a838c4Schristos free(type);
15200a838c4Schristos free(data);
153ca32bd8dSchristos }
154ca32bd8dSchristos }
155e4d43b82Schristos return 0;
156ca32bd8dSchristos }
157ca32bd8dSchristos
158ca32bd8dSchristos /* Encode attributes to buffer */
159e4d43b82Schristos int
encode_attrib(struct sshbuf * b,const Attrib * a)160e4d43b82Schristos encode_attrib(struct sshbuf *b, const Attrib *a)
161ca32bd8dSchristos {
162e4d43b82Schristos int r;
163e4d43b82Schristos
164e4d43b82Schristos if ((r = sshbuf_put_u32(b, a->flags)) != 0)
165e4d43b82Schristos return r;
166e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
167e4d43b82Schristos if ((r = sshbuf_put_u64(b, a->size)) != 0)
168e4d43b82Schristos return r;
169e4d43b82Schristos }
170ca32bd8dSchristos if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
171e4d43b82Schristos if ((r = sshbuf_put_u32(b, a->uid)) != 0 ||
172e4d43b82Schristos (r = sshbuf_put_u32(b, a->gid)) != 0)
173e4d43b82Schristos return r;
174ca32bd8dSchristos }
175e4d43b82Schristos if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
176e4d43b82Schristos if ((r = sshbuf_put_u32(b, a->perm)) != 0)
177e4d43b82Schristos return r;
178e4d43b82Schristos }
179ca32bd8dSchristos if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
180e4d43b82Schristos if ((r = sshbuf_put_u32(b, a->atime)) != 0 ||
181e4d43b82Schristos (r = sshbuf_put_u32(b, a->mtime)) != 0)
182e4d43b82Schristos return r;
183ca32bd8dSchristos }
184e4d43b82Schristos return 0;
185ca32bd8dSchristos }
186ca32bd8dSchristos
187ca32bd8dSchristos /* Convert from SSH2_FX_ status to text error message */
188ca32bd8dSchristos const char *
fx2txt(int status)189ca32bd8dSchristos fx2txt(int status)
190ca32bd8dSchristos {
191ca32bd8dSchristos switch (status) {
192ca32bd8dSchristos case SSH2_FX_OK:
193ca32bd8dSchristos return("No error");
194ca32bd8dSchristos case SSH2_FX_EOF:
195ca32bd8dSchristos return("End of file");
196ca32bd8dSchristos case SSH2_FX_NO_SUCH_FILE:
197ca32bd8dSchristos return("No such file or directory");
198ca32bd8dSchristos case SSH2_FX_PERMISSION_DENIED:
199ca32bd8dSchristos return("Permission denied");
200ca32bd8dSchristos case SSH2_FX_FAILURE:
201ca32bd8dSchristos return("Failure");
202ca32bd8dSchristos case SSH2_FX_BAD_MESSAGE:
203ca32bd8dSchristos return("Bad message");
204ca32bd8dSchristos case SSH2_FX_NO_CONNECTION:
205ca32bd8dSchristos return("No connection");
206ca32bd8dSchristos case SSH2_FX_CONNECTION_LOST:
207ca32bd8dSchristos return("Connection lost");
208ca32bd8dSchristos case SSH2_FX_OP_UNSUPPORTED:
209ca32bd8dSchristos return("Operation unsupported");
210ca32bd8dSchristos default:
211ca32bd8dSchristos return("Unknown status");
212ca32bd8dSchristos }
213ca32bd8dSchristos /* NOTREACHED */
214ca32bd8dSchristos }
215ca32bd8dSchristos
216ca32bd8dSchristos /*
217ca32bd8dSchristos * drwxr-xr-x 5 markus markus 1024 Jan 13 18:39 .ssh
218ca32bd8dSchristos */
219ca32bd8dSchristos char *
ls_file(const char * name,const struct stat * st,int remote,int si_units,const char * user,const char * group)220e160b4e8Schristos ls_file(const char *name, const struct stat *st, int remote, int si_units,
221e160b4e8Schristos const char *user, const char *group)
222ca32bd8dSchristos {
223ca32bd8dSchristos int ulen, glen, sz = 0;
224ca32bd8dSchristos struct tm *ltime = localtime(&st->st_mtime);
2257a183406Schristos char buf[1024], lc[8], mode[11+1], tbuf[12+1], ubuf[11+1], gbuf[11+1];
22634b27b53Sadam char sbuf[FMT_SCALED_STRSIZE];
2278a4530f9Schristos time_t now;
228ca32bd8dSchristos
229ca32bd8dSchristos strmode(st->st_mode, mode);
2307a183406Schristos if (remote) {
231e160b4e8Schristos if (user == NULL) {
232ca32bd8dSchristos snprintf(ubuf, sizeof ubuf, "%u", (u_int)st->st_uid);
233ca32bd8dSchristos user = ubuf;
234e160b4e8Schristos }
235e160b4e8Schristos if (group == NULL) {
236ca32bd8dSchristos snprintf(gbuf, sizeof gbuf, "%u", (u_int)st->st_gid);
237ca32bd8dSchristos group = gbuf;
238e160b4e8Schristos }
2397a183406Schristos strlcpy(lc, "?", sizeof(lc));
2407a183406Schristos } else {
2417a183406Schristos user = user_from_uid(st->st_uid, 0);
2427a183406Schristos group = group_from_gid(st->st_gid, 0);
2437a183406Schristos snprintf(lc, sizeof(lc), "%u", (u_int)st->st_nlink);
244ca32bd8dSchristos }
245ca32bd8dSchristos if (ltime != NULL) {
2468a4530f9Schristos now = time(NULL);
2478a4530f9Schristos if (now - (365*24*60*60)/2 < st->st_mtime &&
2488a4530f9Schristos now >= st->st_mtime)
249ca32bd8dSchristos sz = strftime(tbuf, sizeof tbuf, "%b %e %H:%M", ltime);
250ca32bd8dSchristos else
251ca32bd8dSchristos sz = strftime(tbuf, sizeof tbuf, "%b %e %Y", ltime);
252ca32bd8dSchristos }
253ca32bd8dSchristos if (sz == 0)
254ca32bd8dSchristos tbuf[0] = '\0';
255ee85abc4Schristos ulen = MAXIMUM(strlen(user), 8);
256ee85abc4Schristos glen = MAXIMUM(strlen(group), 8);
25734b27b53Sadam if (si_units) {
25834b27b53Sadam fmt_scaled((long long)st->st_size, sbuf);
2597a183406Schristos snprintf(buf, sizeof buf, "%s %3s %-*s %-*s %8s %s %s",
2607a183406Schristos mode, lc, ulen, user, glen, group,
26134b27b53Sadam sbuf, tbuf, name);
26234b27b53Sadam } else {
2637a183406Schristos snprintf(buf, sizeof buf, "%s %3s %-*s %-*s %8llu %s %s",
2647a183406Schristos mode, lc, ulen, user, glen, group,
265ca32bd8dSchristos (unsigned long long)st->st_size, tbuf, name);
26634b27b53Sadam }
267ca32bd8dSchristos return xstrdup(buf);
268ca32bd8dSchristos }
269