6
6
"encoding/hex"
7
7
"errors"
8
8
"fmt"
9
+ "io"
9
10
"net"
10
11
"os"
11
12
"path/filepath"
@@ -141,7 +142,7 @@ func addXauthEntry(ctx context.Context, fs afero.Fs, host string, display string
141
142
}
142
143
143
144
// Open or create the Xauthority file
144
- file , err := fs .OpenFile (xauthPath , os .O_RDWR | os .O_CREATE | os . O_APPEND , 0o600 )
145
+ file , err := fs .OpenFile (xauthPath , os .O_RDWR | os .O_CREATE , 0o600 )
145
146
if err != nil {
146
147
return xerrors .Errorf ("failed to open Xauthority file: %w" , err )
147
148
}
@@ -153,7 +154,94 @@ func addXauthEntry(ctx context.Context, fs afero.Fs, host string, display string
153
154
return xerrors .Errorf ("failed to decode auth cookie: %w" , err )
154
155
}
155
156
156
- // Write Xauthority entry
157
+ // Read the Xauthority file and look for an existing entry for the host,
158
+ // display, and auth protocol. If an entry is found, overwrite the auth
159
+ // cookie (if it fits). Otherwise, mark the entry for deletion.
160
+ type deleteEntry struct {
161
+ start , end int64
162
+ }
163
+ var deleteEntries []deleteEntry
164
+ pos := int64 (0 )
165
+ updated := false
166
+ for {
167
+ entry , err := readXauthEntry (file )
168
+ if err != nil {
169
+ if errors .Is (err , io .EOF ) {
170
+ break
171
+ }
172
+ return xerrors .Errorf ("failed to read Xauthority entry: %w" , err )
173
+ }
174
+
175
+ nextPos := pos + int64 (entry .Len ())
176
+ cookieStartPos := nextPos - int64 (len (entry .authCookie ))
177
+
178
+ if entry .family == 0x0100 && entry .address == host && entry .display == display && entry .authProtocol == authProtocol {
179
+ if ! updated && len (entry .authCookie ) == len (authCookieBytes ) {
180
+ // Overwrite the auth cookie
181
+ _ , err := file .WriteAt (authCookieBytes , cookieStartPos )
182
+ if err != nil {
183
+ return xerrors .Errorf ("failed to write auth cookie: %w" , err )
184
+ }
185
+ updated = true
186
+ } else {
187
+ // Mark entry for deletion.
188
+ if len (deleteEntries ) > 0 && deleteEntries [len (deleteEntries )- 1 ].end == pos {
189
+ deleteEntries [len (deleteEntries )- 1 ].end = nextPos
190
+ } else {
191
+ deleteEntries = append (deleteEntries , deleteEntry {
192
+ start : pos ,
193
+ end : nextPos ,
194
+ })
195
+ }
196
+ }
197
+ }
198
+
199
+ pos = nextPos
200
+ }
201
+
202
+ // In case the magic cookie changed, or we've previously bloated the
203
+ // Xauthority file, we may have to delete entries.
204
+ if len (deleteEntries ) > 0 {
205
+ // Read the entire file into memory. This is not ideal, but it's the
206
+ // simplest way to delete entries from the middle of the file. The
207
+ // Xauthority file is small, so this should be fine.
208
+ _ , err = file .Seek (0 , io .SeekStart )
209
+ if err != nil {
210
+ return xerrors .Errorf ("failed to seek Xauthority file: %w" , err )
211
+ }
212
+ data , err := io .ReadAll (file )
213
+ if err != nil {
214
+ return xerrors .Errorf ("failed to read Xauthority file: %w" , err )
215
+ }
216
+
217
+ // Delete the entries.
218
+ for _ , entry := range deleteEntries {
219
+ data = append (data [:entry .start ], data [entry .end :]... )
220
+ }
221
+
222
+ // Write the data back to the file.
223
+ _ , err = file .Seek (0 , io .SeekStart )
224
+ if err != nil {
225
+ return xerrors .Errorf ("failed to seek Xauthority file: %w" , err )
226
+ }
227
+ _ , err = file .Write (data )
228
+ if err != nil {
229
+ return xerrors .Errorf ("failed to write Xauthority file: %w" , err )
230
+ }
231
+
232
+ // Truncate the file.
233
+ err = file .Truncate (int64 (len (data )))
234
+ if err != nil {
235
+ return xerrors .Errorf ("failed to truncate Xauthority file: %w" , err )
236
+ }
237
+ }
238
+
239
+ // Return if we've already updated the entry.
240
+ if updated {
241
+ return nil
242
+ }
243
+
244
+ // Append Xauthority entry.
157
245
family := uint16 (0x0100 ) // FamilyLocal
158
246
err = binary .Write (file , binary .BigEndian , family )
159
247
if err != nil {
@@ -198,3 +286,96 @@ func addXauthEntry(ctx context.Context, fs afero.Fs, host string, display string
198
286
199
287
return nil
200
288
}
289
+
290
+ // xauthEntry is an representation of an Xauthority entry.
291
+ //
292
+ // The Xauthority file format is as follows:
293
+ //
294
+ // - 16-bit family
295
+ // - 16-bit address length
296
+ // - address
297
+ // - 16-bit display length
298
+ // - display
299
+ // - 16-bit auth protocol length
300
+ // - auth protocol
301
+ // - 16-bit auth cookie length
302
+ // - auth cookie
303
+ type xauthEntry struct {
304
+ family uint16
305
+ address string
306
+ display string
307
+ authProtocol string
308
+ authCookie []byte
309
+ }
310
+
311
+ func (e xauthEntry ) Len () int {
312
+ // 5 * uint16 = 10 bytes for the family/length fields.
313
+ return 2 * 5 + len (e .address ) + len (e .display ) + len (e .authProtocol ) + len (e .authCookie )
314
+ }
315
+
316
+ func readXauthEntry (r io.Reader ) (xauthEntry , error ) {
317
+ var entry xauthEntry
318
+
319
+ // Read family
320
+ err := binary .Read (r , binary .BigEndian , & entry .family )
321
+ if err != nil {
322
+ return xauthEntry {}, xerrors .Errorf ("failed to read family: %w" , err )
323
+ }
324
+
325
+ // Read address
326
+ var addressLength uint16
327
+ err = binary .Read (r , binary .BigEndian , & addressLength )
328
+ if err != nil {
329
+ return xauthEntry {}, xerrors .Errorf ("failed to read address length: %w" , err )
330
+ }
331
+
332
+ addressBytes := make ([]byte , addressLength )
333
+ _ , err = r .Read (addressBytes )
334
+ if err != nil {
335
+ return xauthEntry {}, xerrors .Errorf ("failed to read address: %w" , err )
336
+ }
337
+ entry .address = string (addressBytes )
338
+
339
+ // Read display
340
+ var displayLength uint16
341
+ err = binary .Read (r , binary .BigEndian , & displayLength )
342
+ if err != nil {
343
+ return xauthEntry {}, xerrors .Errorf ("failed to read display length: %w" , err )
344
+ }
345
+
346
+ displayBytes := make ([]byte , displayLength )
347
+ _ , err = r .Read (displayBytes )
348
+ if err != nil {
349
+ return xauthEntry {}, xerrors .Errorf ("failed to read display: %w" , err )
350
+ }
351
+ entry .display = string (displayBytes )
352
+
353
+ // Read auth protocol
354
+ var authProtocolLength uint16
355
+ err = binary .Read (r , binary .BigEndian , & authProtocolLength )
356
+ if err != nil {
357
+ return xauthEntry {}, xerrors .Errorf ("failed to read auth protocol length: %w" , err )
358
+ }
359
+
360
+ authProtocolBytes := make ([]byte , authProtocolLength )
361
+ _ , err = r .Read (authProtocolBytes )
362
+ if err != nil {
363
+ return xauthEntry {}, xerrors .Errorf ("failed to read auth protocol: %w" , err )
364
+ }
365
+ entry .authProtocol = string (authProtocolBytes )
366
+
367
+ // Read auth cookie
368
+ var authCookieLength uint16
369
+ err = binary .Read (r , binary .BigEndian , & authCookieLength )
370
+ if err != nil {
371
+ return xauthEntry {}, xerrors .Errorf ("failed to read auth cookie length: %w" , err )
372
+ }
373
+
374
+ entry .authCookie = make ([]byte , authCookieLength )
375
+ _ , err = r .Read (entry .authCookie )
376
+ if err != nil {
377
+ return xauthEntry {}, xerrors .Errorf ("failed to read auth cookie: %w" , err )
378
+ }
379
+
380
+ return entry , nil
381
+ }
0 commit comments