tinycm view index.cgi @ rev 32

Warning on first install if content is not writable
author Christophe Lincoln <pankso@slitaz.org>
date Mon Jan 06 20:48:49 2014 +0000 (2014-01-06)
parents 9d1c1115562a
children 6b640e0c98c8
line source
1 #!/bin/sh
2 #
3 # TinyCM - Small, fast and elegent CGI/SHell Content Manager
4 #
5 # Copyright (C) 2012-2014 SliTaz GNU/Linux - BSD License
6 #
7 . /usr/lib/slitaz/httphelper
9 # Let's have a peer site config file with a .cgi extension so content
10 # is secure even if left in a web server directory.
11 . config.cgi
13 tiny="$PWD"
14 po="en fr"
15 content="content"
16 wiki="$content/wiki"
17 index="index"
18 cache="cache"
19 plugins="plugins"
20 tmp="/tmp/tinycm"
21 sessions="$tmp/sessions"
22 script="$SCRIPT_NAME"
23 activity="$cache/log/activity.log"
25 # Content negotiation for Gettext
26 IFS=","
27 for lang in $HTTP_ACCEPT_LANGUAGE
28 do
29 lang=${lang%;*} lang=${lang# } lang=${lang%-*}
30 if echo "$po" | fgrep -q "$lang"; then
31 break
32 fi
33 case "$lang" in
34 en) lang="C" ;;
35 fr) lang="fr_FR" ;;
36 pt) lang="pt_BR" ;;
37 ru) lang="ru_RU" ;;
38 esac
39 done
40 unset IFS
41 export LANG=$lang LC_ALL=$lang
43 #
44 # Functions
45 #
47 # Used by edit to display language name and the language box. This is
48 # for CM content not gettext support.
49 get_lang() {
50 lang=$(echo $d | cut -d "/" -f 1)
51 doc=${d#$lang/}
52 echo '<div id="lang">'
53 for l in $LANGUAGES
54 do
55 case $lang in
56 en) i18n="English" ;;
57 fr) i18n="Français" ;;
58 pt) i18n="Português" ;;
59 ru) i18n="Русский" ;;
60 *) i18n="*" ;;
61 esac
62 echo "<a href='?d=$l/$doc'>$l</a>"
63 done
64 echo '</div>'
65 }
67 # HTML 5 header.
68 html_header() {
69 if [ -f "$tiny/lib/header.html" ]; then
70 cat $tiny/lib/header.html | sed -e s!'%TITLE%'!"$TITLE - $d"!g
71 else
72 cat << EOT
73 <!DOCTYPE html>
74 <html xmlns="http://www.w3.org/1999/xhtml">
75 <head>
76 <title>$TITLE</title>
77 <meta charset="utf-8" />
78 <style type="text/css">body { margin: 40px 120px; }</style>
79 </head>
80 <body>
81 <!-- Content -->
82 <div id="content">
83 EOT
84 fi
85 }
87 # HTML 5 footer.
88 html_footer() {
89 if [ -f "$tiny/lib/footer.html" ]; then
90 cat $tiny/lib/footer.html
91 else
92 cat << EOT
94 <!-- End content -->
95 </div>
97 <div id="footer">&hearts;</div>
99 </body>
100 </html>
101 EOT
102 fi
103 }
105 # Default index if missing
106 default_index() {
107 mkdir -p "$wiki"
108 cat > $wiki/$index.txt << EOT
109 ==== Welcome ====
111 This is the default index page of your TinyCM, you can login then start to
112 edit and adding some content. You can read the help about text formating
113 and functions: <a href='?d=en/help'>Help page</a>
115 EOT
116 }
118 # Log main activity.
119 log_activity() {
120 [ -d "$cache/log" ] || mkdir -p ${cache}/log
121 #gravatar="$(get_gravatar $MAIL 24)"
122 grep ^[A-Z] | \
123 sed s"#^[A-Z]\([^']*\)#$user|$(date '+%Y-%m-%d')|\0#" \
124 >> $cache/log/activity.log
125 }
127 # Log documents activity.
128 log() {
129 grep ^[A-Z] | \
130 sed s"#^[A-Z]\([^']*\)#$(date '+%Y-%m-%d %H:%M') : \0#" \
131 >> $cache/$d/activity.log
132 }
134 # Check if user is auth
135 check_auth() {
136 auth="$(COOKIE auth)"
137 user="$(echo $auth | cut -d ":" -f 1)"
138 md5cookie="$(echo $auth | cut -d ":" -f 2)"
139 [ -f "$sessions/$user" ] && md5session="$(cat $sessions/$user)"
140 if [ "$md5cookie" == "$md5session" ] && [ "$auth" ]; then
141 . $PEOPLE/$user/account.conf
142 return 0
143 else
144 return 1
145 fi
146 }
148 # Check if user is admin
149 admin_user() {
150 fgrep -q 'ADMIN_USER="yes"' ${PEOPLE}/${user}/account.conf
151 }
153 # Authentified or not
154 user_box() {
155 if check_auth; then
156 cat << EOT
158 <div id="user">
159 <a href="$script?user=$user">$(get_gravatar $MAIL 20)</a>
160 <a href="$script?logout">Logout</a>
161 </div>
163 EOT
164 else
165 cat << EOT
167 <div id="user">
168 <a href="$script?login"><img src="images/avatar.png" alt="[ User ]" /></a>
169 <a href="$script?login">Login</a>
170 </div>
172 EOT
173 fi
174 cat << EOT
175 <!--
176 <div id="search">
177 <form method="get" action="$script">
178 <input type="text" name="search" placeholder="$(gettext "Search")" />
179 </form>
180 </div>
181 -->
182 EOT
183 }
185 # Link for online signup if enabled.
186 online_signup() {
187 if [ "$ONLINE_SIGNUP" == "yes" ]; then
188 echo -n "<p><a href='$script?signup'>"
189 gettext "Create a new account"
190 echo '</a></p>'
191 fi
192 }
194 # Login page
195 login_page() {
196 cat << EOT
197 <h2>$(gettext "Login")</h2>
199 <div id="account-info">
200 $(gettext "No account yet or trouble with you account? Please send
201 a request to $ADMIN_MAIL with your real name, user name, mail and password.")
202 $(online_signup)
203 </div>
205 <div id="login">
206 <form method="post" action="$script">
207 <input type="text" name="auth" placeholder="$(gettext "User name")" />
208 <input type="password" name="pass" placeholder="$(gettext "Password")" />
209 <div>
210 <input type="submit" value="Login" /> $error
211 </div>
212 </form>
213 </div>
215 <div style="clear: both;"></div>
216 EOT
217 }
219 # Signup page
220 signup_page() {
221 cat << EOT
223 <div id="signup">
224 <form method="post" name="signup" action="$script" onsubmit="return checkSignup();">
225 <input type="hidden" name="signup" value="new" />
226 <input type="text" name="name" placeholder="$(gettext "Real name")" />
227 <input type="text" name="user" placeholder="$(gettext "User name")" />
228 <input type="text" name="mail" placeholder="$(gettext "Email")" />
229 <input type="password" name="pass" placeholder="$(gettext "Password")" />
230 <div>
231 <input type="submit" value="$(gettext "Create new account")" />
232 </div>
233 </form>
234 </div>
236 EOT
237 }
239 # Create a new user in AUTH_FILE and PEOPLE
240 new_user_config() {
241 if [ ! -f "$AUTH_FILE" ];then
242 touch $(DESTDIR)$(LOGIN)/auth/people
243 chmod 0600 $(DESTDIR)$(LOGIN)/auth/people
244 fi
245 key=$(echo -n "$user:$mail:$pass" | md5sum | awk '{print $1}')
246 echo "$user:$pass" >> $AUTH_FILE
247 mkdir -p $PEOPLE/$user/
248 cat > $PEOPLE/$user/account.conf << EOT
249 # SliTaz user configuration
250 #
252 NAME="$name"
253 USER="$user"
254 MAIL="$mail"
255 KEY="$key"
257 EOT
258 chmod 0600 $PEOPLE/$user/account.conf
259 # First created user is admin
260 if [ $(ls ${PEOPLE} | wc -l) == "1" ]; then
261 echo 'ADMIN_USER="yes"' >> $PEOPLE/$user/account.conf
262 fi
263 }
265 # Display user public profile.
266 public_people() {
267 cat << EOT
268 <pre>
269 Real name : $NAME
270 </pre>
271 EOT
272 # Display personnal user profile
273 if [ -f "$PEOPLE/$USER/profile.txt" ]; then
274 cat $PEOPLE/$USER/profile.txt | wiki_parser
275 fi
276 }
278 # Display authentified user profile. TODO: change password
279 auth_people() {
280 cat << EOT
281 <pre>
282 Real name : $NAME
283 Email : $MAIL
284 Secure key : $KEY
285 </pre>
286 EOT
287 # Each user can have personal profile page
288 if [ -f "$PEOPLE/$USER/profile.txt" ]; then
289 cat $PEOPLE/$USER/profile.txt | wiki_parser
290 cat << EOT
291 <div id="tools">
292 <a href="$script?edit=profile">$(gettext "Edit profile")</a>
293 </div>
294 EOT
295 else
296 cat << EOT
297 <div id="tools">
298 <a href="$script?edit=profile">$(gettext "Create a profile page")</a>
299 </div>
300 EOT
301 fi
302 }
304 # The CM style parser. Just a title, simple text formating and internal
305 # links, as well as images and use HTML for other stuff. Keep it fast!
306 # To make TinyCM as easy as possible we have a small HTML editor/helper
307 # written in Javascript
308 wiki_parser() {
309 doc="[0-9a-zA-Z\.\#/~\_%=\?\&,\+\:@;!\(\)\*\$'\-]*"
310 sed \
311 -e s"#====\([^']*\)====#<h2>\1</h2>#"g \
312 -e s"#===\([^']*\)===#<h3>\1</h3>#"g \
313 -e s"#==\([^']*\)==#<h4>\1</h4>#"g \
314 -e s"#\*\*\([^']*\)\*\*#<b>\1</b>#"g \
315 -e s"#''\([^']*\)''#<em>\1</em>#"g \
316 -e s"#__\([^']*\)__#<u>\1</u>#"g \
317 -e s"#\[\([^]]*\)|\($doc\)\]#<a href='$script?d=\2'>\1</a>#"g \
318 -e s"#\[\([^]]*\)!\($doc\)\]#<a href='\2'>\1</a>#"g \
319 -e s"#\[\(http://*[^]]*.png\)\]#<img src='\1' />#"g \
320 -e s"#\[\([^]]*.png\)\]#<img src='content/cloud/\1' />#"g
321 }
323 link_user() {
324 echo "<a href='$(basename $script)?user=$user'>$user</a>"
325 }
327 # Save a document. Do we need more than 1 backup and diff ?
328 save_document() {
329 mkdir -p $cache/$d $(dirname $wiki/$d)
330 # May be a new page.
331 if [ ! -f "$wiki/$d.txt" ]; then
332 new=0
333 touch $wiki/$d.txt
334 fi
335 cp $wiki/$d.txt $cache/$d/last.bak
336 sed "s/$(echo -en '\r') /\n/g" > $wiki/$d.txt << EOT
337 $(GET content)
338 EOT
339 diff $cache/$d/last.bak $wiki/$d.txt > $cache/$d/last.diff
340 # Log
341 if [ "$new" ]; then
342 echo "Page created by: $(link_user)" | log
343 echo "New document: <a href='$script?d=$d'>$d</a>" | log_activity
344 if [ "$HG" == "yes" ]; then
345 cd $content && hg -q add
346 hg commit -q -u "$NAME <$MAIL>" -m "Created new document: $d"
347 cd $tiny
348 fi
349 else
350 # Here we may clean log: cat && tail -n 40
351 echo "Page edited by: $(link_user)" | log
352 if [ "$HG" == "yes" ]; then
353 cd $content && hg commit -q -u "$NAME <$MAIL>" \
354 -m "Edited document: $d"
355 cd $tiny
356 fi
357 fi
358 }
360 # Save a user profile.
361 save_profile() {
362 path="$PEOPLE/$user"
363 cp -f ${path}/${d}.txt ${path}/${d}.bak
364 sed "s/$(echo -en '\r') /\n/g" > ${path}/${d}.txt << EOT
365 $(GET content)
366 EOT
367 }
369 # CM tools (edit, diff, etc).
370 wiki_tools() {
371 cat << EOT
372 <div id="tools">
373 <a href="$script?edit=$d">$(gettext "Edit document")</a>
374 <a href="$script?diff=$d">$(gettext "Last diff")</a>
375 <a href="$script?log=$d">$(gettext "File log")</a>
376 <a href="$script?dashboard">$(gettext "Dashboard")</a>
377 $([ "$HG" == "yes" ] && echo "<a href='$script?hg'>Hg Log</a>")
378 </div>
379 EOT
380 }
382 # Get and display Gravatar image: get_gravatar email size
383 # Link to profile: <a href="http://www.gravatar.com/$md5">...</a>
384 get_gravatar() {
385 email=$1
386 size=$2
387 [ "$size" ] || size=48
388 url="http://www.gravatar.com/avatar"
389 md5=$(md5crypt $email)
390 echo "<img src='$url/$md5?d=identicon&s=$size' alt='&lowast;' />"
391 }
393 # List hg logs
394 hg_log() {
395 cd $content
396 cat << EOT
397 <table>
398 <thead>
399 <td>$(gettext "User")</td>
400 <td>$(gettext "Description")</td>
401 <td>$(gettext "Revision")</td>
402 </thead>
403 EOT
404 hg log --template "<tr><td>{author}</td><td>{desc}</td><td>{rev}</td></tr>\n"
405 echo '</table>'
406 }
408 #
409 # POST actions
410 #
412 case " $(POST) " in
413 *\ auth\ *)
414 # Authenticate user. Create a session file in $sessions to be used
415 # by check_auth. We have the user login name and a peer session
416 # md5 string in the COOKIE.
417 user="$(POST auth)"
418 pass="$(md5crypt "$(POST pass)")"
419 valid=$(fgrep "${user}:" $AUTH_FILE | cut -d ":" -f 2)
420 if [ "$pass" == "$valid" ] && [ "$pass" != "" ]; then
421 md5session=$(echo -n "$$:$user:$pass:$$" | md5sum | awk '{print $1}')
422 [ -d $sessions ] || mkdir -p $sessions
423 echo "$md5session" > $sessions/$user
424 header "Location: $script" \
425 "Set-Cookie: auth=$user:$md5session; HttpOnly"
426 else
427 header "Location: $script?login&error"
428 fi ;;
429 *\ signup\ *)
430 # POST action for signup
431 name="$(POST name)"
432 user="$(POST user)"
433 mail="$(POST mail)"
434 pass="$(md5crypt "$(POST pass)")"
435 if ! grep "^${user}:" $AUTH_FILE; then
436 new_user_config
437 header "Location: $script?login"
438 else
439 header
440 html_header
441 user_box
442 echo "<h2>gettext "User already exists: $user"</h2>"
443 html_footer
444 fi ;;
445 esac
447 #
448 # Plugins
449 #
450 for p in $(ls -1 $plugins)
451 do
452 [ -f "$plugins/$p/$p.conf" ] && . $plugins/$p/$p.conf
453 [ -x "$plugins/$p/$p.cgi" ] && . $plugins/$p/$p.cgi
454 done
456 #
457 # GET actions
458 #
460 case " $(GET) " in
461 *\ edit\ *)
462 d="$(GET edit)"
463 header
464 html_header
465 user_box
466 get_lang
467 if check_auth; then
468 if [ "$doc" == "profile" ]; then
469 wiki="$PEOPLE/$user"
470 fi
471 cat << EOT
472 <h2>$(gettext "Edit $doc [ $i18n ]")</h2>
474 <div id="edit">
476 <form method="get" action="$script" name="editor">
477 <input type="hidden" name="save" value="$d" />
478 <textarea name="content">$(cat "$wiki/$d.txt")</textarea>
479 <input type="submit" value="$(gettext "Save document")" />
480 $(gettext "Code Helper:")
481 $(cat lib/jseditor.html)
482 </form>
484 </div>
485 EOT
486 else
487 gettext "You must be logged in to edit pages"
488 fi
489 html_footer ;;
491 *\ save\ *)
492 d="$(GET save)"
493 if check_auth; then
494 # User profile
495 if [ "$d" == "profile" ]; then
496 save_profile
497 header "Location: $script?user=$user"
498 else
499 save_document
500 fi
501 fi
502 header "Location: $script?d=$d" ;;
504 *\ log\ *)
505 d="$(GET log)"
506 header
507 html_header
508 user_box
509 # Main activity
510 if [ "$d" == "log" ]; then
511 echo "<h2>$(gettext "Activity")</h2>"
512 if check_auth; then
513 echo "<div id='tools'>"
514 echo "<a href='$script?dashboard'>Dashboard</a>"
515 echo "</div>"
516 fi
517 echo '<pre>'
518 if [ -f "$cache/log/activity.log" ]; then
519 IFS="|"
520 tac $cache/log/activity.log | while read USER DATE LOG
521 do
522 . ${PEOPLE}/${USER}/account.conf
523 cat << EOT
524 <a href='$script?user=$USER'>$(get_gravatar $MAIL 24)</a>\
525 <span class='date'>$DATE -</span> $LOG
526 EOT
527 done
528 unset IFS
529 else
530 gettext "No activity log yet"; echo
531 fi
532 echo '</pre>'
533 html_footer && exit 0
534 fi
535 get_lang
536 echo "<h2>$(gettext "Activity for:") <a href='$script?d=$d'>$d</a></h2>"
537 echo '<pre>'
538 if [ -f "$cache/$d/activity.log" ]; then
539 tac $cache/$d/activity.log
540 else
541 gettext "No log for: $d"; echo
542 fi
543 echo '</pre>'
544 if check_auth; then
545 wiki_tools
546 fi
547 html_footer ;;
549 *\ ls\ *)
550 d="Document list"
551 header
552 html_header
553 user_box
554 echo "<h2>$(gettext "Document list")</h2>"
555 if check_auth; then
556 echo "<div id='tools'>"
557 echo "<a href='$script?dashboard'>Dashboard</a>"
558 echo "</div>"
559 fi
560 echo '<pre>'
561 cd ${wiki}
562 for d in $(find . -type f | sed s'/.\///')
563 do
564 echo "<a href='$script?d=${d%.txt}'>${d%.txt}</a>"
565 done
566 echo '</pre>'
567 html_footer ;;
569 *\ diff\ *)
570 d="$(GET diff)"
571 date="last"
572 header
573 html_header
574 user_box
575 get_lang
576 echo "<h2>$(gettext "Diff for:") <a href='$script?d=$d'>$d</a></h2>"
577 echo '<pre>'
578 if [ -f "$cache/$d/$date.diff" ]; then
579 cat $cache/$d/$date.diff | sed \
580 -e 's|&|\&amp;|g' -e 's|<|\&lt;|g' -e 's|>|\&gt;|g' \
581 -e s"#^-\([^']*\).#<span style='color: red;'>\0</span>#"g \
582 -e s"#^+\([^']*\).#<span style='color: green;'>\0</span>#"g \
583 -e s"#@@\([^']*\)@@#<span style='color: blue;'>@@\1@@</span>#"g
584 else
585 gettext "No diff for: $d"; echo
586 fi
587 echo '</pre>'
588 if check_auth; then
589 wiki_tools
590 fi
591 html_footer ;;
593 *\ login\ *)
594 # The login page
595 d="Login"
596 [ "$(GET error)" ] && \
597 error="<p class="error">$(gettext "Bad login or pass")</p>"
598 header
599 html_header
600 user_box
601 login_page
602 html_footer ;;
604 *\ signup\ *)
605 # The login page
606 d="$(gettext "Sign Up")"
607 header
608 html_header
609 user_box
610 echo "<h2>$d</h2>"
611 if [ "$ONLINE_SIGNUP" == "yes" ]; then
612 signup_page
613 else
614 gettext "Online registration is disabled"
615 fi
616 html_footer ;;
618 *\ logout\ *)
619 # Set a Cookie in the past to logout.
620 expires="Expires=Wed, 01-Jan-1980 00:00:00 GMT"
621 if check_auth; then
622 rm -f "$sessions/$user"
623 header "Location: $script" "Set-Cookie: auth=none; $expires; HttpOnly"
624 fi ;;
626 *\ user\ *)
627 # User profile
628 d="$(GET user)"
629 header
630 html_header
631 user_box
632 . $PEOPLE/"$(GET user)"/account.conf
633 echo "<h2>$(get_gravatar $MAIL) $(GET user)</h2>"
634 loglines=$(fgrep $(GET user) $(find $cache -name *.log) | wc -l)
635 gettext "Activities:"; echo " $loglines"
636 if check_auth && [ "$(GET user)" == "$user" ]; then
637 auth_people
638 else
639 # check_auth will set VARS to current logged user: re-source
640 . $PEOPLE/"$(GET user)"/account.conf
641 public_people
642 fi
643 html_footer ;;
645 *\ dashboard\ *)
646 # For now simply list plugins and users info. We could have a
647 # dashbord only for ADMINS found in the config file. The dashboard
648 # should also be a plugin.
649 d="Dashboard"
650 header
651 html_header
652 user_box
653 users=$(ls -1 $PEOPLE | wc -l)
654 docs=$(find $wiki -type f | wc -l)
655 wikisize="$(du -sh $wiki | awk '{print $1}')"
656 cachesize="$(du -sh $cache | awk '{print $1}')"
657 [ "$HG" != "yes" ] && hg=$(gettext "disabled")
658 [ "$HG" == "yes" ] && hg=$(gettext "enabled")
659 echo "<h2>$d</h2>"
660 if check_auth; then
661 cat << EOT
662 <div id="tools">
663 <a href='$script?log'>Activity log</a>
664 <a href='$script?ls'>List files</a>
665 </div>
667 <pre>
668 Users : $users
669 Wiki : $docs ($wikisize)
670 Cache : $cachesize
671 Mercurial : $hg
672 </pre>
673 <h3>Admin users</h3>
674 EOT
675 # Get the list of administrators
676 for u in $(ls $PEOPLE)
677 do
678 user=${u}
679 if admin_user; then
680 echo "<a href='?user=$u'>$u</a>"
681 fi
682 done
683 cat << EOT
684 <h3>$(gettext "Plugins")</h3>
685 <pre>
686 EOT
687 for p in $(ls -1 $plugins)
688 do
689 . $plugins/$p/$p.conf
690 echo "<a href='?$p'>$PLUGIN</a> - $SHORT_DESC"
691 done
692 echo '</pre>'
693 else
694 gettext "You must be logged in to view the dashboard."
695 fi
696 html_footer ;;
698 *\ hg\ *)
699 header
700 [ "$HG" != "yes" ] && gettext "Hg is disabled" && exit 0
701 [ ! -x /usr/bin/hg ] && gettext "Hg is not installed" && exit 0
702 d="Hg Log"
703 html_header
704 user_box
705 echo "<h2>$d</h2>"
706 case " $(GET hg) " in
707 *\ init\ *)
708 if check_auth; then
709 [ -d "$content/.hg" ] && exit 0
710 echo '<pre>'
711 gettext "Executing: hg init"; echo
712 cd $content/ && hg init
713 echo '[hooks]' > .hg/hgrc
714 echo 'incoming = hg update' >> .hg/hgrc
715 gettext "Adding current content and committing"; echo
716 [ ! -f "$wiki/index.txt" ] && touch $wiki/$index.txt
717 hg add && hg commit -u "$NAME <$MAIL>" \
718 -m "Initial commit with current content"
719 echo '</pre>' && cd ..
720 fi ;;
721 esac
722 hg_log
723 html_footer ;;
725 *)
726 # Display requested page
727 d="$(GET d)"
728 [ "$d" ] || d=$index
729 header
730 html_header
731 user_box
732 get_lang
733 # Generate a default index on first run.
734 if [ ! -f "$wiki/$index.txt" ]; then
735 default_index
736 if [ ! -f "$wiki/$index.txt" ]; then
737 echo "<pre class='error'>Directory: content/ is not writable</pre>"
738 html_footer && exit 0
739 fi
740 fi
741 if [ ! -f "$wiki/$d.txt" ]; then
742 echo "<h2>$d</h2>"
743 gettext "The document does not exist. You can create it or read the"
744 echo " <a href='?d=en/help'>help</a>"
745 else
746 if fgrep -q [NOWIKI] $wiki/$d.txt; then
747 cat $wiki/$d.txt | sed '/[NOWIKI]/'d
748 else
749 cat $wiki/$d.txt | wiki_parser
750 fi
751 fi
752 if check_auth; then
753 wiki_tools
754 if [ "$HG" == "yes" ] && [ ! -d "$content/.hg" ]; then
755 echo '<p class="error box">'
756 gettext "Mercurial is enabled but no repository found"
757 echo ": <a href='?hg=init'>Hg init</a>"
758 echo '</p>'
759 fi
760 fi
761 html_footer ;;
762 esac
764 exit 0