跳转至

2020

linux 添加打印机

记一次arch 上添加打印机的过程, 公司有一个Ricoh c5503 的打印机,ip 192.168.4.99 通过nmap这个地址可以发现开了不少端口

PORT     STATE SERVICE
21/tcp   open  ftp
23/tcp   open  telnet
80/tcp   open  http
139/tcp  open  netbios-ssn
514/tcp  open  shell
515/tcp  open  printer
631/tcp  open  ipp
7443/tcp open  oracleas-https
8080/tcp open  http-proxy
9100/tcp open  jetdirect

启动avahi-daemon 服务

这个服务提供mDNS功能, 可以发现局域网内的主机提供的服务,例如打印服务,ssh。 各个主机通过224.0.0.251 地址多播自己提供的服务
主机也可以配置自己想广告的服务,这是用户可选的。 具体看wiki

通过执行一下命令可以看到打印服务

 avahi-browse --all --ignore-local --resolve --terminate 

找到有用的信息如下:

+    br0 IPv4 RICOH MP C4503 [002673734B1D]                 Web Site             local
=    br0 IPv4 RICOH MP C4503 [002673734B1D]                 Web Site             local
   hostname = [RNP002673734B1D.local]
   address = [192.168.4.99]
   port = [80]
   txt = ["path=/"]

安装cupsd

启动cups-browsed.service服务后, 可以通过网页也配置打印机, 包括分享自己的打印机和添加别人的打印机。 我这里需要添加打印机服务

  1. 浏览器输入http://localhost:631/ 可以打开网页
  2. administrator -> add printer 输入root和密码
  3. 有以下选项
    Backend Error Handler
    Internet Printing Protocol (ipp)
    LPD/LPR Host or Printer
    Internet Printing Protocol (ipps)
    AppSocket/HP JetDirect
    Internet Printing Protocol (http)
    Internet Printing Protocol (https)
    Windows Printer via SAMBA 
    
  4. 我这里选择的AppSocket/HP JetDirect, 因为nmap显示是支持jetdirect的。
  5. 输入地址socket://192.168.4.99:9100
  6. 输入自定义的打印机名称和路径/

至此,浏览器打印页面就可以看到添加的打印机了

分享打印机

没有分享的时候,只有本机可以使用,当想要给手机使用时,可以勾选分享。此时CUPS会使用mdns广播打印机服务到主机所处的网段,所以手机也必须在这个网段里。 cups_share.png

更新

开始时我走的ipp协议,打印机地址为'ipp://192.168.4.99:631/ipp', 后来连不上打印机

通过查端口发现有维护人员修改了打印机端口信息, 631和80端口都被封了,可能是出于安全考虑

PORT      STATE    SERVICE
21/tcp    open     ftp
23/tcp    open     telnet
80/tcp    filtered http
139/tcp   open     netbios-ssn
514/tcp   open     shell
515/tcp   open     printer
631/tcp   filtered ipp
7443/tcp  open     oracleas-https
8080/tcp  open     http-proxy
9100/tcp  open     jetdirect
65000/tcp open     unknown
631没有开放,参考windows的同事配置,发现他们用的9100端口(9100是走socket协议),所以需要重新添加打印机
新的网络地址即为'socket://127.0.0.1:9100'

更新2

之前是自己搓的桌面环境,需要手动在CUPS的web界面添加打印机。而自从使用了完整桌面GNOME发现非常简单。GNOME自带有打印机配置工具,只需要填入打印机的IP(也许由于不在一个网段),就会自动检测支持的协议并配置好。

Pasted image 20240410175417.png

oauth2 认证说明

oauth2 是依赖第三方的认证方式, 例如玩游戏的时候弹出QQ登录,微信登录。 这种游戏运营商并不需要用户注册,而是直接从QQ或者微信那里获取用户的OPENID, 然后游戏游戏运营商存储并通过OPENID来识别用户

而且, 有资质的游戏运营商还可以通过玩家的openid来获取用户的信息, 例如用户的手机号,网名,年龄等信息。 但有资质这个是有QQ和微信来决定的,游戏运营商需要先去腾讯那里注册认证。腾讯愿意给游戏运营商分享多少信息是腾讯说了算。 所以有用户的数据在手, 腾讯稳稳当国内老大

以QQ登录为例,当玩家登录游戏时,游戏运营商先让用户访问QQ的auth2服务器,并带上游戏运营商的id。待QQ认证后会回调运营商注册的回调接口 一般为oauth/callback,并且带上用户的openid。 这样游戏运营商就知道是谁登录了。

如果游戏运营商需要更多用户资料时,例如注册, 运营商可以通过QQ的查询接口,密钥以及用户的openid 去查询。 这样就拉取到了用户的信息。 然后如果资料不全, 再让玩家补充,例如输入身份证号。这应该国家不准腾讯向别人分享的,必须要用户自己输入。。

使用github的oauth认证

  1. 先去github -> developer 创建oauth应用, 输入自己的回调地址。 当用户被github认证后,会调用这个地址

  2. 在服务端配置,利用一个开源 oauth2_proxy工具, 项目地址:

https://github.com/oauth2-proxy/oauth2-proxy
  1. 配置nginx

配置`oauth2_proxy

之前遇到问题,一直授权不成功, 原因是cookie_secure 配成了true, 即使是https下。原因未知

auth_logging = true
auth_logging_format = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
pass_basic_auth = true
# pass_user_headers = true
## pass the request Host Header to upstream
## when disabled the upstream Host is used as the Host Header
pass_host_header = true
## Email Domains to allow authentication for (this authorizes any email on this domain)
## for more granular authorization use `authenticated_emails_file`
## To authorize any email addresses use "*"
# email_domains = [
#     "yourcompany.com"
# ]
email_domains="*"
## The OAuth Client ID, Secret
provider="github"
client_id = "cef54714c84e3b0c2248"
client_secret = "a96d3d94771273b5295202d03c0c2d3ca7f625dc"
## Pass OAuth Access token to upstream via "X-Forwarded-Access-Token"
pass_access_token = false
## Authenticated Email Addresses File (one email per line)
# authenticated_emails_file = ""
## Htpasswd File (optional)
## Additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
## enabling exposes a username/login signin form
# htpasswd_file = ""
## Templates
## optional directory with custom sign_in.html and error.html
# custom_templates_dir = ""
## skip SSL checking for HTTPS requests
# ssl_insecure_skip_verify = false
## Cookie Settings
## Name     - the cookie name
## Secret   - the seed string for secure cookies; should be 16, 24, or 32 bytes
##            for use with an AES cipher when cookie_refresh or pass_access_token
##            is set
## Domain   - (optional) cookie domain to force cookies to (ie: .yourcompany.com)
## Expire   - (duration) expire timeframe for cookie
## Refresh  - (duration) refresh the cookie when duration has elapsed after cookie was initially set.
##            Should be less than cookie_expire; set to 0 to disable.
##            On refresh, OAuth token is re-validated.
##            (ie: 1h means tokens are refreshed on request 1hr+ after it was set)
## Secure   - secure cookies are only sent by the browser of a HTTPS connection (recommended)
## HttpOnly - httponly cookies are not readable by javascript (recommended)
# cookie_name = "_oauth2_proxy"
cookie_secret = "beautyfly"
cookie_domains = "beautyflying.cn"
cookie_expire = "168h"
# cookie_refresh = ""
cookie_secure = false
# cookie_httponly = true

可以将oauth2 配置成服务

[Unit]
Description = OAuth2 proxy for www blog

[Service]
Type=simple
ExecStart=/usr/bin/oauth2_proxy -config /etc/oauth2-proxy.cfg
[Install]
WantedBy=multi-user.target

nginx 配置

    location /oauth2/ {
            proxy_pass       http://127.0.0.1:4180;
            proxy_set_header Host                    $host;
            proxy_set_header X-Real-IP               $remote_addr;
            proxy_set_header X-Scheme                $scheme;
            proxy_set_header X-Auth-Request-Redirect $request_uri;
        # or, if you are handling multiple domains:
        # proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
    }

    location = /oauth2/auth {
    proxy_pass       http://127.0.0.1:4180;
    proxy_set_header Host             $host;
    proxy_set_header X-Real-IP        $remote_addr;
    proxy_set_header X-Scheme         $scheme;
    # nginx auth_request includes headers but not body
    proxy_set_header Content-Length   "";
    proxy_pass_request_body           off;
    }

    location / {

    auth_request /oauth2/auth;
    error_page 401 = /oauth2/sign_in;

    # pass information via X-User and X-Email headers to backend,
    # requires running with --set-xauthrequest flag
    auth_request_set $user   $upstream_http_x_auth_request_user;
    auth_request_set $email  $upstream_http_x_auth_request_email;
    proxy_set_header X-User  $user;
    proxy_set_header X-Email $email;

    # if you enabled --pass-access-token, this will pass the token to the backend
    auth_request_set $token  $upstream_http_x_auth_request_access_token;
    proxy_set_header X-Access-Token $token;

    # if you enabled --cookie-refresh, this is needed for it to work with auth_request
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header Set-Cookie $auth_cookie;

    # When using the --set-authorization-header flag, some provider's cookies can exceed the 4kb
    # limit and so the OAuth2 Proxy splits these into multiple parts.
    # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
    # so if your cookies are larger than 4kb, you will need to extract additional cookies manually.
    auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;

    # Extract the Cookie attributes from the first Set-Cookie header and append them
    # to the second part ($upstream_cookie_* variables only contain the raw cookie content)
    if ($auth_cookie ~* "(; .*)") {
        set $auth_cookie_name_0 $auth_cookie;
        set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1";
    }

    # Send both Set-Cookie headers now if there was a second part
    if ($auth_cookie_name_upstream_1) {
        add_header Set-Cookie $auth_cookie_name_0;
        add_header Set-Cookie $auth_cookie_name_1;
    }

        root   /usr/share/nginx/html/blog;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

使用baidu 的oauth 认证

使用正则来校验输入合法性

软件加固里面经常使用的办法就是校验用户输入,例如要求名词需要是大小写或_开头, 支持包含大小写 ,中划线 -,下划线_以及点.

那么相应的正则表到式为^[a-zA-Z_][-a-zA-Z1-9_.]*$,校验方法为

echo $1 | grep -q '^[a-zA-Z_][-a-zA-Z1-9_.]*$'
test $? -eq 0 && echo "yes" || echo "no"

用python实现

Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> print(re.match('^[a-zA-Z_][a-zA-Z0-9\-_.]*$', '_abcdedf.', flags=0))
<re.Match object; span=(0, 9), match='_abcdedf.'>
>>> print(re.match('^[a-zA-Z_][a-zA-Z0-9\-_.]*$', '._abcdedf.', flags=0))
None

C++ 小技巧

使用lambda 定义一个可变的const

因为const 不能改变的,只有在程序运行的时候初始化。 而通过lamda,可以通过获取命令行输入来改变值

const int x = []() -> int {
        int t;
        std::cin >> t;
        return t;
}();

gdb调试容器内的进程

先来个gdb手册链接
这里记录一下自己常用的技巧

恢复debug符号

通常上线的程序要strip符号表来减少内存占用和硬盘占用,会执行以下命令

## 1. 只保留debug表的内容(可选),为以后调试用,生产时不用
objcopy --only-keep-debug foo foo.dbg

## 2. 将foo的debug表删除,不要任何调试信息,用于生产使用
objcopy --strip-debug  foo 

如果生产出问题了,就需要恢复debug信息,add-gnu-debuglink参数可以是foo.dbg, 也可以就是strip之前的foo文件
所以第一步是可选的

## 3. 恢复debug信息
objcopy --add-gnu-debuglink=foo.dbg foo

但其实第三步也是可选的,只要把foo.dbg放到foo相同位置时,gdb就可以读取到debug信息

使用.gdbinit

.gdbinit 文件是类似.bash_profile的文件,一般在home目录或者项目根目录, gdb 自动读取
也可以手动读取其他位置的, 在gdb执行source /somedir/.gdbinit

通常可以在这个文件里定义一些设置例如set pagination off 关闭输出分页, 就不用每次交互确定
还可以定义命令 如pvector可以打印vector内的所有成员, 非常比较方便

gdb 调试容器内的进程

因为容器内运行的程序都是strip了debug符号的,所以之前调试都是先将debug文件复制到容器对应位置,然后将源码也映射到容器内,而且还要安装gdb和重新开启priveledge模式, 非常繁琐。
所以尝试如下操作:
1. 能不能单独一个容器安装gdb和符号和代码文件, 跨容器去调试另一个容器的进程呢? 2. 能不能在宿主机上直接调试容器进程呢?

经过一番搜索,没有第一种方案的实践办法,但第二种方案亲测可行!

步骤

  1. 先启动容器,一般业务主进程的pid为1
  2. 在容器外面执行ps aux | grep xxx,因为在很多情况是多个容器跑同一个程序,所以ps aux 通过参数名来确定进程的pid, 当然最好的方式是通过进程namespace了
  3. 使用sudo gdb -p pid, 提示缺少Missing separate debuginfo for target
  4. 将debug符号文件复制到容器对应容器的目录
  5. 再次attach,提示缺少源文件/builds/project/src/aaa.cpp,这是cmake编译时的目录,需要替换
  6. 在gdb里执行命令 set substitute-path /builds/ /home/my-home/ # from -> to
  7. 然后使用l可以查看对应的源代码了

总结

可以直接在宿主机上运行gdb来调试容器进程,并替换代码路径为宿主机的目录。

ps: gdb 启动时默认会读取~/.gdbinit 文件, 但是gdb会提示, 这是由于调试docker需要sudo权限,而我的gdbinit在非root目录

warning: File "/home/h/.gdbinit" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".                                                        
To enable execution of this file add                                                                                                                                                          
        add-auto-load-safe-path /home/h/.gdbinit                                                                                                                                            
line to your configuration file "/root/.gdbinit".                                                                                                                                             
To completely disable this security protection add                                                                                                                                            
        set auto-load safe-path /                                                                                                                                                             
line to your configuration file "/root/.gdbinit".                                                                                                                                             

也就是需要手动执行set auto-load safe-path / 或者将 add-auto-load-safe-path /home/h/.gdbinit写入/root/.gdbinit

Git的重要操作

Git登录信息

在临时环境下拉代码, 如果重新生成密钥再添加到托管平台比较麻烦,要么拷贝自己的私钥过来,更加不安全。 这时候临时用密码登录最适合了, 但是每个pull fetch操作都要登录,这很烦躁,所以下面有2个方法解决

设置密码暂存

指类似sudo命令一样, 校验成功之后一段时间,不需要输入密码

git config --global   credential.helper 'cache --timeout 900'
我们都知道Git 的配置分3种级别 local system global, 这里推荐global。 因为只设置local,在有submodule的情况下,同样会提示输入密码。

设置密码永存

不推荐

git config --global   credential.helper store

git为不同项目配置不同的用户名和邮箱

电脑中同时有个人项目和公司项目时,有时候会导致commit 消息里面的邮件搞串了。可以通过git配置不同目录下的项目使用不同的信息。参考

gcc 会优化很多内存相关的调用

他们通常在'string.h'里,比如memcpy, 当编译器通过上下文能推断出传入参数类型,libc的mempcy就会替换为gcc的memcpy, 这可能导致问题

举例

知乎评论的,release版本的memset被替换, 导致跟debug版本不同行为bug

https://www.zhihu.com/question/435258463/answer/1969561682

内存地址对齐

如果内存地址对齐的,那么'取值地址被取值长度求模为0',例如int i, 则要求(void*)&i % sizeof(int) == 0

原因

当同时定义多个变量时,不考虑编译器优化,则他们在内存的位置相邻。按照不同的对齐长度,有不同的占用大小
这里以结构体举例

struct bar{
    int interger;
        char charactor;
        long long longlong;
};
使用#pragma pack(4) 设置按4字节对齐,默认也是4字节,那么sizeof(struct abc) == 16 使用gdb打印内存地址,可见地址都对齐的: '0x7fffffffe430&4==0' '0x7fffffffe438%8==0'
(gdb) p sizeof(struct abc)
$5 = 16
(gdb) p &a.interger 
$1 = (int *) 0x7fffffffe430
(gdb) p &a.charactor 
$2 = 0x7fffffffe434 "\232\177"
(gdb) p &a.longlong 
$3 = (long long *) 0x7fffffffe438
(gdb) p &a
$4 = (abc *) 0x7fffffffe430

顺便提一下,指针指向的是内存段的低地址,所以有个面试题是判断系统是大端小端的方法如下
通过如下强转int为char, 如果c==0x12则为大端,否则为小端系统

int i=0x12345678;
char c=(char)i;

使用#pragma pack(1) 设置为按1字节对齐,则上面的结构体大小为'13',显然更省内存了
但通过取地址会发现地址未对齐, 求模不为0。 但我自己使用gcc测试地址依旧对齐的,原因是编译器会进行自然 对齐

自然对齐

上面的#pragma pack(1)虽然使得longlong的地址对于bar来说偏移了5字节,但编译器始终能调整bar的起始地址使得longlong的地址对齐

595fac88 
c3c5a2d8
105d3ab8

不对齐的坏处

虽然上面说了编译器能自然对齐,但还是会真的出现不对齐访问。由于对内存的访问涉及到具体硬件的实现,所以一般会有几种影响 1. 硬件平台支持非对齐访问,但性能有损耗 2. 抛出异常给cpu,由cpu来校准,性能有更大损耗 3. 平台不支持,但也不抛异常,获取的是错误的值,导致难定位的异常

引用

https://www.kernel.org/doc/html/latest/core-api/unaligned-memory-access.html