summaryrefslogtreecommitdiffstats
path: root/.vim/bundle/SudoEdit/autoload/SudoEdit.vim
blob: a136c6e1a391c15a9440dca9f39b6601a53dc932 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
" SudoEdit.vim - Use sudo/su for writing/reading files with Vim
" ---------------------------------------------------------------
" Version:  0.19
" Authors:  Christian Brabandt <cb@256bit.org>
" Last Change: Wed, 14 Aug 2013 22:29:27 +0200
" Script:  http://www.vim.org/scripts/script.php?script_id=2709 
" License: VIM License
" GetLatestVimScripts: 2709 19 :AutoInstall: SudoEdit.vim

" Functions: "{{{1

let s:dir=fnamemodify(expand("<sfile>"), ':p:h')

fu! <sid>Init() "{{{2
" Which Tool for super-user access to use
" Will be tried in order, first tool that is found will be used
" (e.g. you could use ssh)
" You can specify one in your .vimrc using the
" global variable g:sudoAuth

"    each time check, whether the authentication
"    method changed (e.g. the User set a variable)
"    if !exists("s:AuthTool") 
        let s:sudoAuth=" sudo su "
        if <sid>Is("mac")
            let s:sudoAuth = "security ". s:sudoAuth
        elseif <sid>Is("win")
            let s:sudoAuth = "runas elevate ". s:sudoAuth
        endif
        if exists("g:sudoAuth")
            let s:sudoAuth = g:sudoAuth .' '. s:sudoAuth 
        endif

        " Specify the parameter to use for the auth tool e.g. su uses "-c", but
        " for su, it will be autodetected, sudo does not need one, for ssh use 
        " "root@localhost"
        "
        " You can also use this parameter if you do not want to become root 
        " but any other user
        "
        " You can specify this parameter in your .vimrc using the
        " global variable g:sudoAuthArg
        if !exists("g:sudoAuthArg")
            let s:sudoAuthArg=""
        else
            let s:sudoAuthArg=g:sudoAuthArg
        endif

        let s:AuthTool = <sid>CheckAuthTool(split(s:sudoAuth, '\s'))
        if empty(s:AuthTool)
            call <sid>echoWarn("No authentication tool found, aborting!")
            throw "sudo:noTool"
        endif
        if s:AuthTool[0] == "su" && empty(s:sudoAuthArg)
            let s:sudoAuthArg="-c"
        elseif s:AuthTool[0] == "security" && empty(s:sudoAuthArg)
            let s:sudoAuthArg="execute-with-privileges"
        elseif s:AuthTool[0] == "runas" && empty(s:sudoAuthArg)
            let s:sudoAuthArg = "/noprofile /user:\"Administrator\""
        endif
        if <sid>Is("win")
            if !exists("s:writable_file")
                " Write into public directory so everybody can access it
                " easily
                let s:writable_file = (empty(expand("$PUBLIC")) ? 
                            \ expand("$TEMP") : expand("$PUBLIC") ).
                            \ '\vim_temp.txt'
                let s:writable_file = shellescape(fnamemodify(s:writable_file, ':p:8'))
            endif
        else
            let s:writable_file = tempname()
        endif

        call <sid>SudoAskPasswd()
        call add(s:AuthTool, s:sudoAuthArg . " ")
        if !exists("s:error_dir")
            let s:error_dir = tempname()
            call <sid>Mkdir(s:error_dir)
            let s:error_file = s:error_dir. '/error'
            if <sid>Is("win")
                let s:error_file = s:error_dir. '\error'
                let s:error_file = fnamemodify(s:error_file, ':p:8')
            endif
        endif
"    endif
    " Stack of messages
    let s:msg = []
endfu

fu! <sid>Mkdir(dir) "{{{2
    " First remove the directory, it might still be there from last call
    let dir = shellescape(a:dir)
    call SudoEdit#Rmdir(dir)
    call system("mkdir ". dir)
    " Clean up on Exit
    if !exists('#SudoEditExit#VimLeave')
        augroup SudoEditExit
            au!
            " Clean up when quitting Vim
            exe "au VimLeave * :call SudoEdit#Rmdir(".dir. ")"
        augroup END
    endif
endfu

fu! <sid>LocalSettings(values, readflag) "{{{2
    if empty(a:values)
        " Set shellrediraction temporarily
        " This is used to get su working right!
        let o_srr = &srr
        " avoid W11 warning
        let o_ar  = &l:ar
        let &srr = '>'
        setl ar
        let o_tti = &t_ti
        let o_tte = &t_te
        " Turn off screen switching
        set t_ti= t_te=
        " Set shell to something sane (zsh, doesn't allow to override files using
        " > redirection, issue #24, hopefully POSIX sh works everywhere)
        let o_shell = &shell
        if !<sid>Is("win")
            set shell=sh
        endif
        call <sid>Init()
        return [o_srr, o_ar, o_tti, o_tte, o_shell]
    else
        " Make sure, persistent undo information is written
        " but only for valid files and not empty ones
        let file=substitute(expand("%"), '^sudo:', '', '')
        try
            if has("persistent_undo")
            let undofile = undofile(file)
            if !empty(file) &&
                \!<sid>CheckNetrwFile(@%) && !empty(undofile) &&
                \ &l:udf
                if !a:readflag
                " Force reading in the buffer to avoid stupid W13 warning
                " don't do this in GUI mode, so one does not have to enter
                " the password again (Leave the W13 warning)
                if !has("gui_running") && exists("s:new_file") && s:new_file
                    "sil call <sid>SudoRead(file)
                    " Be careful, :e! within a BufWriteCmd can crash Vim!
                    exe "e!" file
                endif
                if empty(glob(undofile)) &&
                    \ &undodir =~ '^\.\($\|,\)'
                    " Can't create undofile
                    call add(s:msg, "Can't create undofile in current " .
                    \ "directory, skipping writing undofiles!")
                    throw "sudo:undofileError"
                endif
                call <sid>Exec("wundo! ". fnameescape(undofile(file)))
                if empty(glob(fnameescape(undofile(file))))
                    " Writing undofile not possible 
                    call add(s:msg,  "Error occured, when writing undofile")
                    return
                endif
                if <sid>is("unix") && !empty(undofile)
                    let ufile = string(shellescape(undofile, 1))
                    let perm = system("stat -c '%u:%g' " .
                        \ shellescape(file, 1))[:-2]
                    " Make sure, undo file is readable for current user
                    let cmd  = printf("!%s sh -c 'test -f %s && ".
                        \ "chown %s -- %s && ",
                        \ join(s:AuthTool, ' '), ufile, perm, ufile)
                    let cmd .= printf("chmod a+r -- %s 2>%s'", ufile, shellescape(s:error_file))
                    if has("gui_running")
                        call <sid>echoWarn("Enter password again for".
                            \ " setting permissions of the undofile")
                    endif
                    call <sid>Exec(cmd)
                    "call system(cmd)
                endif
                endif
            endif
            endif " has("persistent_undo")
        catch
            " no-op
        finally
            " Make sure W11 warning is triggered and consumed by 'ar' setting
            checktime
            " Reset old settings
            " shellredirection
            let &srr  = a:values[0]
            " Screen switchting codes, and shell
            let [ &t_ti, &t_te, &shell ] = a:values[2:4]
            " Reset autoread option
            let &l:ar = a:values[1]
        endtry
    endif
endfu

fu! <sid>CheckAuthTool(Authlist) "{{{2
    for tool in a:Authlist
        if executable(tool) ||
        \   (tool == 'uac' && <sid>Is("win")) "enable experimental support for UAC on windows
            return [tool]
        endif
    endfor
    echoerr "No tool found for authentication. Is sudo/su installed and in your $PATH?"
    echoerr "Try setting g:sudoAuth and g:sudoAuthArg"
    return []
endfu

fu! <sid>echoWarn(mess) "{{{2
    echohl WarningMsg
    echomsg a:mess
    echohl Normal
endfu

fu! <sid>SudoRead(file) "{{{2
    sil %d _
    if <sid>Is("win")
        let file=shellescape(fnamemodify(a:file, ':p:8'))
        let cmd= '!'. s:dir.'\sudo.cmd dummy read '. file. 
            \ ' '. s:writable_file.  ' '.
            \ join(s:AuthTool, ' ')
    else
        let cmd='cat ' . shellescape(a:file,1) . ' 2>'. shellescape(s:error_file)
        if  s:AuthTool[0] =~ '^su$'
            let cmd='"' . cmd . '" --'
        endif
        let cmd=':0r! ' . join(s:AuthTool, ' ') . cmd
    endif
    call <sid>Exec(cmd)
    if v:shell_error
        echoerr "Error reading ". a:file . "! Password wrong?"
        throw "sudo:readError"
    endif
    if <sid>Is("win")
        if !filereadable(s:writable_file[1:-2])
            call add(s:msg, "Temporary file ". s:writable_file. 
                        \ " does not exist. Probably access was denied!")
            throw "sudo:readError"
        else
            exe ':0r ' s:writable_file[1:-2]
        endif
    endif
    sil $d _
    if has("persistent_undo")
        " Force reading undofile, if one exists
        if filereadable(undofile(a:file))
            exe "sil rundo" escape(undofile(a:file), '%')
        endif
    endif
    filetype detect
    set nomod
endfu

fu! <sid>SudoWrite(file) range "{{{2
    if  s:AuthTool[0] == 'su'
    " Workaround since su cannot be run with :w !
        exe a:firstline . ',' . a:lastline . 'w! ' . s:writable_file
        let cmd=':!' . join(s:AuthTool, ' ') . '"mv ' . s:writable_file . ' ' .
            \ shellescape(a:file,1) . '" --'
    else
        if <sid>Is("win")
            exe a:firstline . ',' . a:lastline . 'w! ' . s:writable_file[1:-2]
            let cmd= '!'. s:dir.'\sudo.cmd dummy write '. shellescape(fnamemodify(a:file, ':p:8')).
                \ ' '. s:writable_file. ' '. join(s:AuthTool, ' ')
        else
            let cmd=printf('tee >/dev/null 2>%s %s',shellescape(s:error_file), shellescape(a:file,1))
            let cmd=a:firstline . ',' . a:lastline . 'w !' .
            \ join(s:AuthTool, ' ') . cmd
        endif
    endif
    if <sid>CheckNetrwFile(a:file) && exists(":NetUserPass") == 2
        let protocol = matchstr(a:file, '^[^:]:')
        call <sid>echoWarn('Using Netrw for writing')
        let uid = input(protocol . ' username: ')
        let passwd = inputsecret('password: ')
        call NetUserPass(uid, passwd)
        " Write using Netrw
        w
    else
        let s:new_file = 0
        if empty(glob(a:file))
            let s:new_file = 1
        endif
        call <sid>Exec(cmd)
    endif
    if v:shell_error
        throw "sudo:writeError"
    endif
    " Write successful
    if &mod
        setl nomod
    endif
endfu

fu! <sid>Stats(file) "{{{2
    ":w echoes a string like this by default:
    ""SudoEdit.vim" 108L, 2595C geschrieben
    return '"' . a:file . '" ' . line('$') . 'L, ' . getfsize(expand(a:file)) . 'C written'
endfu

fu! <sid>Is(os) "{{{2
    if (a:os == "win")
        return has("win32") || has("win16") || has("win64")
    elseif (a:os == "mac")
        return has("mac") || has("macunix")
    elseif (a:os == "unix")
        return has("unix") || has("macunix")
    endif
endfu

fu! <sid>Mes(msg) "{{{2
    if !(exists("g:sudoDebug") && g:sudoDebug)
        redr!
        if empty(s:msg)
            return
        endif
    endif
    for mess in a:msg
        echo mess
    endfor
    let s:msg=[]
endfu

fu! <sid>Exception(msg) "{{{2
    echohl Error
    echomsg a:msg
    if exists("g:sudoDebug") && g:sudoDebug
        echo v:throwpoint
    else
        echohl Normal
        call input("Hit enter to continue")
    endif
    echohl Normal
endfu

fu! <sid>CheckNetrwFile(file) "{{{2
    return a:file =~ '^\%(dav\|fetch\|ftp\|http\|rcp\|rsync\|scp\|sftp\):'
endfu

fu! <sid>SudoAskPasswd() "{{{2
    if s:AuthTool[0] != 'sudo' ||
    \ s:AuthTool[0] =~ 'SUDO_ASKPASS' ||
    \ ( exists("g:sudo_no_gui") && g:sudo_no_gui == 1) ||
    \ !has("unix") ||
    \ !exists("$DISPLAY")
        return
    endif

    let askpwd = ["/usr/lib/openssh/gnome-ssh-askpass",
        \ "/usr/bin/ksshaskpass",
        \ "/usr/lib/ssh/x11-ssh-askpass" ]
    if exists("g:sudo_askpass")
        let askpwd = insert(askpwd, g:sudo_askpass, 0)
    endif
    let sudo_arg = '-A'
    let sudo_askpass = expand("$SUDO_ASKPASS")
    if sudo_askpass != "$SUDO_ASKPASS"
        let list = [ sudo_askpass ] + askpwd
    else
        let list = askpwd
    endif
    for item in list
        if executable(item)
            " give environment value to sudo, so -A knows
            " which program to call
            if (s:AuthTool[0] !~ 'SUDO_ASKPASS')
                call insert(s:AuthTool, 'SUDO_ASKPASS='.shellescape(item,1), 0)
                call add(s:AuthTool, '-A')
                return
            endif
        endif
    endfor
endfu

fu! <sid>Exec(cmd) "{{{2
    let cmd = a:cmd
    if exists("g:sudoDebug") && g:sudoDebug
        let cmd = substitute(a:cmd, '2>'.shellescape(s:error_file), '', 'g')
        let cmd = 'verb '. cmd
        call <sid>echoWarn(cmd)
        exe cmd
    " probably hit-enter prompt is shown
    else
        if has("gui_running")
            exe cmd
        else
            silent exe cmd
            " avoid hit-enter prompt
            redraw!
        endif
    endif
    if filereadable(s:error_file) && getfsize(s:error_file) > 0
        let error=readfile(s:error_file)
        call add(s:msg, join(error, "\n"))
        call delete(s:error_file)
    endif
endfu
fu! SudoEdit#Rmdir(dir) "{{{2
    if <sid>Is("win")
        sil! call system("rd /s /q ". a:dir)
    else
        sil! call system("rm -rf -- ". a:dir)
    endif
endfu

fu! SudoEdit#SudoDo(readflag, force, file) range "{{{2
    try
        let _settings=<sid>LocalSettings([], 1)
    catch /sudo:noTool/
        call <sid>LocalSettings(_settings, a:readflag)
        return
    endtry
    let s:use_sudo_protocol_handler = 0
    if empty(a:file)
        let file = expand("%")
    else
        let file = expand(a:file)
        if empty(file)
            let file = a:file " expand() might fail (issue #17)
        endif
        if file =~ '^sudo:'
            let s:use_sudo_protocol_handler = 1
            let file = substitute(file, '^sudo:', '', '')
        endif
        let file = fnamemodify(file, ':p')
    endif
    if empty(file)
        call <sid>echoWarn("Cannot write file. Please enter filename for writing!")
        call <sid>LocalSettings(_settings, 1)
        return
    endif
    try
        if a:readflag
            if !&mod || !empty(a:force)
                call <sid>SudoRead(file)
            else
                call add(s:msg, "Buffer modified, not reloading!")
                return
            endif
        else
            if !&mod && empty(a:force) && empty(a:file)
                call add(s:msg, "Buffer not modified, not writing!")
                return
            endif
            exe a:firstline . ',' . a:lastline . 'call <sid>SudoWrite(file)'
            call add(s:msg, <sid>Stats(file))
        endif
    catch /sudo:writeError/
        call <sid>Exception("There was an error writing the file!")
        return
    catch /sudo:readError/
        call <sid>Exception("There was an error reading the file ". file. " !")
        return
    finally
        " Delete temporary file
        call delete(s:writable_file)
        call <sid>LocalSettings(_settings, a:readflag)
        call <sid>Mes(s:msg)
    endtry
    if file !~ 'sudo:' && s:use_sudo_protocol_handler
        let file = 'sudo:' . fnamemodify(file, ':p')
    endif
    if s:use_sudo_protocol_handler ||
        \ empty(expand("%")) ||
        \ file != expand("%")
        exe ':sil f ' . file
        filetype detect
    endif
endfu

" Modeline {{{1
" vim: set fdm=marker fdl=0 ts=4 sts=4 sw=4 et:  }}}