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