Commit 3fcab6f1 authored by runge's avatar runge

x11vnc: -users sslpeer= option. RFB_SSL_CLIENT_CERT, -ncache 10 default

parent 2d0b184f
2007-04-28 Karl Runge <runge@karlrunge.com>
* x11vnc: -users sslpeer= option. RFB_SSL_CLIENT_CERT var.
X11VNC_FINDDISPLAY_ALWAYS_FAILS var. -ncache default 10.
gid switch fix.
* ssvnc: Linux.i*86 fix and code sync.
2007-04-07 Karl Runge <runge@karlrunge.com> 2007-04-07 Karl Runge <runge@karlrunge.com>
* x11vnc: add gnome, kde, etc. FINDCREATEDISPLAY tags. * x11vnc: add gnome, kde, etc. FINDCREATEDISPLAY tags.
In check_ncache periodically check for changed desktop. In check_ncache periodically check for changed desktop.
......
This diff is collapsed.
...@@ -413,6 +413,11 @@ int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input, ...@@ -413,6 +413,11 @@ int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input,
} else { } else {
set_env("RFB_STATE", "UNKNOWN"); set_env("RFB_STATE", "UNKNOWN");
} }
if (certret_str) {
set_env("RFB_SSL_CLIENT_CERT", certret_str);
} else {
set_env("RFB_SSL_CLIENT_CERT", "");
}
/* set RFB_CLIENT_PORT to peer port for command to use */ /* set RFB_CLIENT_PORT to peer port for command to use */
if (cd && cd->client_port > 0) { if (cd && cd->client_port > 0) {
...@@ -491,6 +496,9 @@ int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input, ...@@ -491,6 +496,9 @@ int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input,
/* gone, accept, afteraccept */ /* gone, accept, afteraccept */
ok = 0; ok = 0;
if (!strcmp(mode, "env")) {
return 1;
}
if (!strcmp(mode, "accept") && cmd_ok("accept")) { if (!strcmp(mode, "accept") && cmd_ok("accept")) {
ok = 1; ok = 1;
} }
......
...@@ -869,7 +869,8 @@ void print_help(int mode) { ...@@ -869,7 +869,8 @@ void print_help(int mode) {
"\n" "\n"
" In this mode you can set X11VNC_SKIP_DISPLAY to a comma\n" " In this mode you can set X11VNC_SKIP_DISPLAY to a comma\n"
" separated list of displays (e.g. \":0,:1\") to ignore\n" " separated list of displays (e.g. \":0,:1\") to ignore\n"
" in the finding process.\n" " in the finding process. This can also be set by the\n"
" user via \"nd=\" using \"-\" instead of \",\"\n"
"\n" "\n"
" An interesting option is WAIT:cmd=FINDCREATEDISPLAY\n" " An interesting option is WAIT:cmd=FINDCREATEDISPLAY\n"
" that is like FINDDISPLAY in that is uses the same method\n" " that is like FINDDISPLAY in that is uses the same method\n"
...@@ -906,6 +907,10 @@ void print_help(int mode) { ...@@ -906,6 +907,10 @@ void print_help(int mode) {
"\n" "\n"
" Where /.../x11vnc is the full path to x11vnc.\n" " Where /.../x11vnc is the full path to x11vnc.\n"
"\n" "\n"
" If for some reason you do not want x11vnc to ever\n"
" try to find an existing display set the env. var\n"
" X11VNC_FINDDISPLAY_ALWAYS_FAILS=1 (also -env ...)\n"
"\n"
" Use WAIT:cmd=FINDCREATEDISPLAY-print to print out the\n" " Use WAIT:cmd=FINDCREATEDISPLAY-print to print out the\n"
" script used. You can specify the preferred order via\n" " script used. You can specify the preferred order via\n"
" e.g., WAIT:cmd=FINDCREATEDISPLAY-Xdummy,Xvfb,X and/or\n" " e.g., WAIT:cmd=FINDCREATEDISPLAY-Xdummy,Xvfb,X and/or\n"
...@@ -1643,6 +1648,28 @@ void print_help(int mode) { ...@@ -1643,6 +1648,28 @@ void print_help(int mode) {
" If you want to limit which users this will be done for,\n" " If you want to limit which users this will be done for,\n"
" provide them as a comma separated list after \"unixpw=\"\n" " provide them as a comma separated list after \"unixpw=\"\n"
"\n" "\n"
" Similarly, in -ssl mode, if \"-users sslpeer=\" is\n"
" supplied then after an SSL client authenticates with his\n"
" cert (the -sslverify option is required for this) x11vnc\n"
" will extract a UNIX username from the \"emailAddress\"\n"
" field (username@hostname.com) of the \"Subject\" in the\n"
" x509 SSL cert and then try to switch to that user as\n"
" though \"-users +username\" had been supplied. If you\n"
" want to limit which users this will be done for, provide\n"
" them as a comma separated list after \"sslpeer=\".\n"
" Set the env. var X11VNC_SSLPEER_CN to use the Common\n"
" Name (normally a hostname) instead of the Email field.\n"
" NOTE: the x11vnc administrator must take great care\n"
" that any client certs he adds to -sslverify have the\n"
" correct UNIX username in the \"emailAddress\" field\n"
" of the cert. Otherwise a user may be able to log in\n"
" as another. The following command can be of use in\n"
" checking: \"openssl x509 -text -in file.crt\", see the\n"
" \"Subject:\" line. Also, along with the normal RFB_*\n"
" env. vars. (see -accept) passed to external cmd=\n"
" commands, RFB_SSL_CLIENT_CERT will be set to the\n"
" client's x509 certificate string.\n"
"\n"
" To immediately switch to a user *before* connections\n" " To immediately switch to a user *before* connections\n"
" to the X display are made or any files opened use the\n" " to the X display are made or any files opened use the\n"
" \"=\" character: \"-users =bob\". That user needs to\n" " \"=\" character: \"-users =bob\". That user needs to\n"
...@@ -2175,6 +2202,8 @@ void print_help(int mode) { ...@@ -2175,6 +2202,8 @@ void print_help(int mode) {
" for rapid retrieval. So a W x H frambuffer is expanded\n" " for rapid retrieval. So a W x H frambuffer is expanded\n"
" to a W x (n+1)*H one. Use 0 to disable. Default: XXX.\n" " to a W x (n+1)*H one. Use 0 to disable. Default: XXX.\n"
"\n" "\n"
" The \"n\" is actually optional, the default is 10.\n"
"\n"
" For this and the other -ncache* options below you can\n" " For this and the other -ncache* options below you can\n"
" abbreviate \"-ncache\" with \"-nc\". Also, \"-nonc\"\n" " abbreviate \"-ncache\" with \"-nc\". Also, \"-nonc\"\n"
" is the same as \"-ncache 0\"\n" " is the same as \"-ncache 0\"\n"
......
...@@ -234,9 +234,9 @@ Solaris, FreeBSD, etc. Unpack your archive and see the subdirectories of ...@@ -234,9 +234,9 @@ Solaris, FreeBSD, etc. Unpack your archive and see the subdirectories of
./bin ./bin
for the ones that were shipped in this project, e.g. ./bin/Linux.i686 for the ones that were shipped in this project, e.g. ./bin/Linux.i686
Run "uname -sm" to see your OS+arch combination. (See the Run "uname -sm" to see your OS+arch combination (n.b. all Linux x86 are
./bin/ssvnc_cmd -h output for how to override platform autodection mapped to Linux.i686). (See the ./bin/ssvnc_cmd -h output for how to
via the UNAME env. var). override platform autodection via the UNAME env. var).
External Dependencies: External Dependencies:
......
...@@ -25,16 +25,12 @@ if [ "X$FULLNAME" = "XKarl J. Runge" ]; then ...@@ -25,16 +25,12 @@ if [ "X$FULLNAME" = "XKarl J. Runge" ]; then
VNCVIEWER_POPUP_FIX=1 VNCVIEWER_POPUP_FIX=1
export VNCVIEWER_POPUP_FIX export VNCVIEWER_POPUP_FIX
#if uname -smr | grep 'Linux 2\.4.*i686' > /dev/null; then
# UNAME="Linux.i686.older"
# export UNAME
#fi
PATH=`echo "$PATH" | sed -e 's,runge/bin/override,-------------,'` PATH=`echo "$PATH" | sed -e 's,runge/bin/override,-------------,'`
fi fi
if [ "X$WISH" = "X" ]; then if [ "X$WISH" = "X" ]; then
WISH=wish WISH=wish
for try in wish wish8.3 wish8.4 wish8.5 for try in wish wish8.3 wish8.4 wish8.5 wish8.6
do do
if type $try > /dev/null; then if type $try > /dev/null; then
WISH=$try WISH=$try
...@@ -53,7 +49,7 @@ export SSVNC_LAUNCH ...@@ -53,7 +49,7 @@ export SSVNC_LAUNCH
# #
name=$UNAME name=$UNAME
if [ "X$name" = "X" ]; then if [ "X$name" = "X" ]; then
name=`uname -sm | sed -e 's/ /./g'` name=`uname -sm | sed -e 's/ /./g' -e 's/Linux\.i.86/Linux.i686/'`
fi fi
f="$0" f="$0"
......
...@@ -113,7 +113,7 @@ fi ...@@ -113,7 +113,7 @@ fi
# #
name=$UNAME name=$UNAME
if [ "X$name" = "X" ]; then if [ "X$name" = "X" ]; then
name=`uname -sm | sed -e 's/ /./g'` name=`uname -sm | sed -e 's/ /./g' -e 's/Linux\.i.86/Linux.i686/'`
fi fi
f="$0" f="$0"
......
...@@ -2760,7 +2760,7 @@ proc launch_unix {hp} { ...@@ -2760,7 +2760,7 @@ proc launch_unix {hp} {
global env global env
set env(SS_VNCVIEWER_RM) $passwdfile set env(SS_VNCVIEWER_RM) $passwdfile
} else { } else {
catch {exec sh -c "sleep 15; rm $passwdfile" &} catch {exec sh -c "sleep 15; rm $passwdfile 2>/dev/null" &}
} }
if {$darwin_cotvnc} { if {$darwin_cotvnc} {
set cmd "$cmd --PasswordFile $passwdfile" set cmd "$cmd --PasswordFile $passwdfile"
......
...@@ -49,7 +49,7 @@ fi ...@@ -49,7 +49,7 @@ fi
# #
name=$UNAME name=$UNAME
if [ "X$name" = "X" ]; then if [ "X$name" = "X" ]; then
name=`uname -sm | sed -e 's/ /./g'` name=`uname -sm | sed -e 's/ /./g' -e 's/Linux\.i.86/Linux.i686/'`
fi fi
if [ "X$name" = "X" ]; then if [ "X$name" = "X" ]; then
echo "cannot determine platform: os.arch, e.g. Linux.i686" echo "cannot determine platform: os.arch, e.g. Linux.i686"
...@@ -71,7 +71,8 @@ if [ -d $dest ]; then ...@@ -71,7 +71,8 @@ if [ -d $dest ]; then
if [ "X$x" = "Xn" ]; then if [ "X$x" = "Xn" ]; then
exit exit
fi fi
rm -rf $dest rm -f $dest/*stunnel*
rm -f $dest/*vncviewer*
fi fi
mkdir -p $dest || exit 1 mkdir -p $dest || exit 1
......
...@@ -220,6 +220,7 @@ int ncache_pad = 0; ...@@ -220,6 +220,7 @@ int ncache_pad = 0;
#endif #endif
int ncache_xrootpmap = NCACHE_XROOTPMAP; int ncache_xrootpmap = NCACHE_XROOTPMAP;
int ncache0 = 0; int ncache0 = 0;
int ncache_default = 10;
int ncache_copyrect = 0; int ncache_copyrect = 0;
int ncache_wf_raises = 1; int ncache_wf_raises = 1;
int ncache_dt_change = 1; int ncache_dt_change = 1;
......
...@@ -162,6 +162,7 @@ extern int wireframe_in_progress; ...@@ -162,6 +162,7 @@ extern int wireframe_in_progress;
extern int ncache; extern int ncache;
extern int ncache0; extern int ncache0;
extern int ncache_default;
extern int ncache_copyrect; extern int ncache_copyrect;
extern int ncache_wf_raises; extern int ncache_wf_raises;
extern int ncache_dt_change; extern int ncache_dt_change;
......
...@@ -1315,6 +1315,11 @@ if (db) fprintf(stderr, "buf: '%s'\n", buf); ...@@ -1315,6 +1315,11 @@ if (db) fprintf(stderr, "buf: '%s'\n", buf);
return 1; return 1;
} }
static char *certret = NULL;
static int certret_fd = -1;
static mode_t omode;
char *certret_str = NULL;
void accept_openssl(int mode, int presock) { void accept_openssl(int mode, int presock) {
int sock = -1, listen = -1, cport, csock, vsock; int sock = -1, listen = -1, cport, csock, vsock;
int status, n, i, db = 0; int status, n, i, db = 0;
...@@ -1456,6 +1461,23 @@ void accept_openssl(int mode, int presock) { ...@@ -1456,6 +1461,23 @@ void accept_openssl(int mode, int presock) {
name = NULL; name = NULL;
} }
if (certret) {
free(certret);
}
if (certret_str) {
free(certret_str);
certret_str = NULL;
}
certret = strdup("/tmp/x11vnc-certret.XXXXXX");
omode = umask(077);
certret_fd = mkstemp(certret);
umask(omode);
if (certret_fd < 0) {
free(certret);
certret = NULL;
certret_fd = -1;
}
/* now fork the child to handle the SSL: */ /* now fork the child to handle the SSL: */
pid = fork(); pid = fork();
...@@ -1785,6 +1807,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface); ...@@ -1785,6 +1807,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface);
signal(SIGALRM, SIG_DFL); signal(SIGALRM, SIG_DFL);
close(csock); close(csock);
if (vsock < 0) { if (vsock < 0) {
rfbLog("SSL: accept_openssl: connection from ssl_helper failed.\n"); rfbLog("SSL: accept_openssl: connection from ssl_helper failed.\n");
rfbLogPerror("accept"); rfbLogPerror("accept");
...@@ -1794,11 +1817,42 @@ if (db) fprintf(stderr, "iface: %s\n", iface); ...@@ -1794,11 +1817,42 @@ if (db) fprintf(stderr, "iface: %s\n", iface);
if (mode == OPENSSL_INETD || ssl_no_fail) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
if (certret_fd >= 0) {
close(certret_fd);
certret_fd = -1;
}
if (certret) {
unlink(certret);
}
return; return;
} }
if (db) fprintf(stderr, "accept_openssl: vsock: %d\n", vsock); if (db) fprintf(stderr, "accept_openssl: vsock: %d\n", vsock);
n = read(vsock, rcookie, strlen(cookie)); n = read(vsock, rcookie, strlen(cookie));
if (certret) {
struct stat sbuf;
sbuf.st_size = 0;
if (certret_fd >= 0 && stat(certret, &sbuf) == 0 && sbuf.st_size > 0) {
certret_str = (char *) malloc(sbuf.st_size+1);
read(certret_fd, certret_str, sbuf.st_size);
close(certret_fd);
certret_fd = -1;
}
if (certret_fd >= 0) {
close(certret_fd);
certret_fd = -1;
}
unlink(certret);
if (certret_str && strstr(certret_str, "NOCERT") == certret_str) {
free(certret_str);
certret_str = NULL;
}
if (0 && certret_str) {
fprintf(stderr, "certret_str[%d]:\n%s\n", sbuf.st_size, certret_str);
}
}
if (n != (int) strlen(cookie) || strncmp(cookie, rcookie, n)) { if (n != (int) strlen(cookie) || strncmp(cookie, rcookie, n)) {
rfbLog("SSL: accept_openssl: cookie from ssl_helper failed. %d\n", n); rfbLog("SSL: accept_openssl: cookie from ssl_helper failed. %d\n", n);
if (errno != 0) { if (errno != 0) {
...@@ -2065,6 +2119,30 @@ if (db > 1) fprintf(stderr, "ssl_init: 4\n"); ...@@ -2065,6 +2119,30 @@ if (db > 1) fprintf(stderr, "ssl_init: 4\n");
} }
rfbLog("SSL: ssl_helper[%d]: SSL_accept() succeeded for: %s\n", getpid(), name); rfbLog("SSL: ssl_helper[%d]: SSL_accept() succeeded for: %s\n", getpid(), name);
if (SSL_get_verify_result(ssl) == X509_V_OK) {
X509 *x;
FILE *cr = NULL;
if (certret != NULL) {
cr = fopen(certret, "w");
}
x = SSL_get_peer_certificate(ssl);
if (x == NULL) {
rfbLog("SSL: ssl_helper[%d]: accepted client %s x509 peer cert is null\n", getpid(), name);
if (cr != NULL) {
fprintf(cr, "NOCERT\n");
fclose(cr);
}
} else {
rfbLog("SSL: ssl_helper[%d]: accepted client %s x509 cert is:\n", getpid(), name);
X509_print_ex_fp(stderr, x, 0, XN_FLAG_MULTILINE);
if (cr != NULL) {
X509_print_ex_fp(cr, x, 0, XN_FLAG_MULTILINE);
fclose(cr);
}
}
}
free(name); free(name);
return 1; return 1;
......
...@@ -14,6 +14,7 @@ extern int openssl_port_num; ...@@ -14,6 +14,7 @@ extern int openssl_port_num;
extern int https_sock; extern int https_sock;
extern pid_t openssl_last_helper_pid; extern pid_t openssl_last_helper_pid;
extern char *openssl_last_ip; extern char *openssl_last_ip;
extern char *certret_str;
extern void raw_xfer(int csock, int s_in, int s_out); extern void raw_xfer(int csock, int s_in, int s_out);
......
...@@ -1295,7 +1295,7 @@ char create_display[] = ...@@ -1295,7 +1295,7 @@ char create_display[] =
"\n" "\n"
"have_root=\"\"\n" "have_root=\"\"\n"
"id0=`id`\n" "id0=`id`\n"
"if id | grep -w root > /dev/null; then\n" "if id | sed -e 's/ gid.*$//' | grep -w root > /dev/null; then\n"
" have_root=\"1\"\n" " have_root=\"1\"\n"
"fi\n" "fi\n"
"\n" "\n"
......
This diff is collapsed.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.TH X11VNC "1" "April 2007" "x11vnc " "User Commands" .TH X11VNC "1" "April 2007" "x11vnc " "User Commands"
.SH NAME .SH NAME
x11vnc - allow VNC connections to real X11 displays x11vnc - allow VNC connections to real X11 displays
version: 0.9.1, lastmod: 2007-04-18 version: 0.9.1, lastmod: 2007-04-27
.SH SYNOPSIS .SH SYNOPSIS
.B x11vnc .B x11vnc
[OPTION]... [OPTION]...
...@@ -1016,7 +1016,8 @@ It is used in the Apache SSL-portal example (see FAQ). ...@@ -1016,7 +1016,8 @@ It is used in the Apache SSL-portal example (see FAQ).
.IP .IP
In this mode you can set X11VNC_SKIP_DISPLAY to a comma In this mode you can set X11VNC_SKIP_DISPLAY to a comma
separated list of displays (e.g. ":0,:1") to ignore separated list of displays (e.g. ":0,:1") to ignore
in the finding process. in the finding process. This can also be set by the
user via "nd=" using "-" instead of ","
.IP .IP
An interesting option is WAIT:cmd=FINDCREATEDISPLAY An interesting option is WAIT:cmd=FINDCREATEDISPLAY
that is like FINDDISPLAY in that is uses the same method that is like FINDDISPLAY in that is uses the same method
...@@ -1055,6 +1056,10 @@ on the machine. E.g. a desktop service: ...@@ -1055,6 +1056,10 @@ on the machine. E.g. a desktop service:
.IP .IP
Where /.../x11vnc is the full path to x11vnc. Where /.../x11vnc is the full path to x11vnc.
.IP .IP
If for some reason you do not want x11vnc to ever
try to find an existing display set the env. var
X11VNC_FINDDISPLAY_ALWAYS_FAILS=1 (also \fB-env\fR ...)
.IP
Use WAIT:cmd=FINDCREATEDISPLAY-print to print out the Use WAIT:cmd=FINDCREATEDISPLAY-print to print out the
script used. You can specify the preferred order via script used. You can specify the preferred order via
e.g., WAIT:cmd=FINDCREATEDISPLAY-Xdummy,Xvfb,X and/or e.g., WAIT:cmd=FINDCREATEDISPLAY-Xdummy,Xvfb,X and/or
...@@ -1866,6 +1871,28 @@ user as though "\fB-users\fR \fI+username\fR" had been supplied. ...@@ -1866,6 +1871,28 @@ user as though "\fB-users\fR \fI+username\fR" had been supplied.
If you want to limit which users this will be done for, If you want to limit which users this will be done for,
provide them as a comma separated list after "unixpw=" provide them as a comma separated list after "unixpw="
.IP .IP
Similarly, in \fB-ssl\fR mode, if "\fB-users\fR \fIsslpeer=\fR" is
supplied then after an SSL client authenticates with his
cert (the \fB-sslverify\fR option is required for this) x11vnc
will extract a UNIX username from the "emailAddress"
field (username@hostname.com) of the "Subject" in the
x509 SSL cert and then try to switch to that user as
though "\fB-users\fR \fI+username\fR" had been supplied. If you
want to limit which users this will be done for, provide
them as a comma separated list after "sslpeer=".
Set the env. var X11VNC_SSLPEER_CN to use the Common
Name (normally a hostname) instead of the Email field.
NOTE: the x11vnc administrator must take great care
that any client certs he adds to \fB-sslverify\fR have the
correct UNIX username in the "emailAddress" field
of the cert. Otherwise a user may be able to log in
as another. The following command can be of use in
checking: "openssl x509 \fB-text\fR \fB-in\fR file.crt", see the
"Subject:" line. Also, along with the normal RFB_*
env. vars. (see \fB-accept)\fR passed to external cmd=
commands, RFB_SSL_CLIENT_CERT will be set to the
client's x509 certificate string.
.IP
To immediately switch to a user *before* connections To immediately switch to a user *before* connections
to the X display are made or any files opened use the to the X display are made or any files opened use the
"=" character: "\fB-users\fR \fI=bob\fR". That user needs to "=" character: "\fB-users\fR \fI=bob\fR". That user needs to
...@@ -2552,6 +2579,8 @@ below the actual framebuffer to cache screen contents ...@@ -2552,6 +2579,8 @@ below the actual framebuffer to cache screen contents
for rapid retrieval. So a W x H frambuffer is expanded for rapid retrieval. So a W x H frambuffer is expanded
to a W x (n+1)*H one. Use 0 to disable. Default: XXX. to a W x (n+1)*H one. Use 0 to disable. Default: XXX.
.IP .IP
The \fIn\fR is actually optional, the default is 10.
.IP
For this and the other \fB-ncache*\fR options below you can For this and the other \fB-ncache*\fR options below you can
abbreviate "\fB-ncache\fR" with "\fB-nc\fR". Also, "\fB-nonc\fR" abbreviate "\fB-ncache\fR" with "\fB-nc\fR". Also, "\fB-nonc\fR"
is the same as "\fB-ncache\fR \fI0\fR" is the same as "\fB-ncache\fR \fI0\fR"
......
...@@ -2248,8 +2248,17 @@ int main(int argc, char* argv[]) { ...@@ -2248,8 +2248,17 @@ int main(int argc, char* argv[]) {
show_dragging = 0; show_dragging = 0;
#ifndef NO_NCACHE #ifndef NO_NCACHE
} else if (!strcmp(arg, "-ncache") || !strcmp(arg, "-nc")) { } else if (!strcmp(arg, "-ncache") || !strcmp(arg, "-nc")) {
CHECK_ARGC if (i < argc-1) {
ncache = atoi(argv[++i]); char *s = argv[i+1];
if (s[0] != '-') {
ncache = atoi(s);
i++;
} else {
ncache = ncache_default;
}
} else {
ncache = ncache_default;
}
if (ncache % 2 != 0) { if (ncache % 2 != 0) {
ncache++; ncache++;
} }
......
...@@ -15,7 +15,7 @@ int xtrap_base_event_type = 0; ...@@ -15,7 +15,7 @@ int xtrap_base_event_type = 0;
int xdamage_base_event_type = 0; int xdamage_base_event_type = 0;
/* date +'lastmod: %Y-%m-%d' */ /* date +'lastmod: %Y-%m-%d' */
char lastmod[] = "0.9.1 lastmod: 2007-04-18"; char lastmod[] = "0.9.1 lastmod: 2007-04-27";
/* X display info */ /* X display info */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment